From 847da3d766b8575afd0aeeed6098e9690fefff8a Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 24 May 2024 23:47:51 +0800 Subject: [PATCH 001/185] Ban unsafe arithmetic operations --- Cargo.toml | 3 + node/src/chain_spec/finney.rs | 6 +- node/src/chain_spec/testnet.rs | 6 +- pallets/admin-utils/src/benchmarking.rs | 1 + pallets/admin-utils/tests/mock.rs | 2 + pallets/collective/src/benchmarking.rs | 3 +- pallets/collective/src/lib.rs | 29 +++-- pallets/collective/src/tests.rs | 2 +- pallets/commitments/src/benchmarking.rs | 7 +- pallets/commitments/src/lib.rs | 13 ++- pallets/commitments/src/types.rs | 7 +- pallets/registry/src/benchmarking.rs | 7 +- pallets/registry/src/lib.rs | 15 +-- pallets/registry/src/types.rs | 7 +- pallets/subtensor/src/benchmarks.rs | 2 +- pallets/subtensor/src/block_step.rs | 95 ++++++++++------ pallets/subtensor/src/delegate_info.rs | 10 +- pallets/subtensor/src/epoch.rs | 36 +++--- pallets/subtensor/src/lib.rs | 29 +++-- pallets/subtensor/src/math.rs | 145 +++++++++++++++--------- pallets/subtensor/src/migration.rs | 11 +- pallets/subtensor/src/registration.rs | 32 +++--- pallets/subtensor/src/root.rs | 56 +++++---- pallets/subtensor/src/serving.rs | 4 +- pallets/subtensor/src/staking.rs | 6 +- pallets/subtensor/src/subnet_info.rs | 2 +- pallets/subtensor/src/uids.rs | 8 +- pallets/subtensor/src/utils.rs | 4 +- pallets/subtensor/src/weights.rs | 23 ++-- pallets/subtensor/tests/block_step.rs | 3 +- pallets/subtensor/tests/difficulty.rs | 2 + pallets/subtensor/tests/epoch.rs | 6 + pallets/subtensor/tests/migration.rs | 2 + pallets/subtensor/tests/mock.rs | 8 +- pallets/subtensor/tests/registration.rs | 2 + pallets/subtensor/tests/root.rs | 2 + pallets/subtensor/tests/senate.rs | 2 + pallets/subtensor/tests/staking.rs | 2 + pallets/subtensor/tests/uids.rs | 2 + pallets/subtensor/tests/weights.rs | 2 + runtime/src/check_nonce.rs | 5 +- runtime/src/lib.rs | 13 ++- runtime/tests/metadata.rs | 2 + runtime/tests/pallet_proxy.rs | 2 + 44 files changed, 398 insertions(+), 228 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56e40c924..51849096a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,10 @@ members = [ resolver = "2" [workspace.lints.clippy] +indexing-slicing = "deny" +arithmetic-side-effects = "deny" type_complexity = "allow" +unwrap-used = "deny" [workspace.dependencies] cargo-husky = { version = "1", default-features = false } diff --git a/node/src/chain_spec/finney.rs b/node/src/chain_spec/finney.rs index 37fa1b073..3694c3d39 100644 --- a/node/src/chain_spec/finney.rs +++ b/node/src/chain_spec/finney.rs @@ -5,7 +5,7 @@ use super::*; pub fn finney_mainnet_config() -> Result { let path: PathBuf = std::path::PathBuf::from("./snapshot.json"); - let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; + let wasm_binary = WASM_BINARY.ok_or("Development wasm not available".to_string())?; // We mmap the file into memory first, as this is *a lot* faster than using // `serde_json::from_reader`. See https://github.com/serde-rs/json/issues/160 @@ -53,7 +53,9 @@ pub fn finney_mainnet_config() -> Result { let key_account = sp_runtime::AccountId32::from(key); processed_balances.push((key_account, *amount)); - balances_issuance += *amount; + balances_issuance = balances_issuance + .checked_add(*amount) + .ok_or("Balances issuance overflowed".to_string())?; } // Give front-ends necessary data to present to users diff --git a/node/src/chain_spec/testnet.rs b/node/src/chain_spec/testnet.rs index 01c95c376..ff6c8cc39 100644 --- a/node/src/chain_spec/testnet.rs +++ b/node/src/chain_spec/testnet.rs @@ -20,7 +20,7 @@ pub fn finney_testnet_config() -> Result { }; let old_state: ColdkeyHotkeys = - json::from_slice(&bytes).map_err(|e| format!("Error parsing genesis file: {}", e))?; + json::from_slice(&bytes).map_err(|e| format!("Error parsing genesis file: {e}"))?; let mut processed_stakes: Vec<( sp_runtime::AccountId32, @@ -53,7 +53,9 @@ pub fn finney_testnet_config() -> Result { let key_account = sp_runtime::AccountId32::from(key); processed_balances.push((key_account, *amount)); - balances_issuance += *amount; + balances_issuance = balances_issuance + .checked_add(*amount) + .ok_or("Balances issuance overflowed".to_string())?; } // Give front-ends necessary data to present to users diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 756864940..0158311f7 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -1,5 +1,6 @@ //! Benchmarking setup #![cfg(feature = "runtime-benchmarks")] +#![allow(clippy::arithmetic_side_effects)] use super::*; #[allow(unused)] diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index c0985b010..c6c3d7323 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -1,3 +1,5 @@ +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] + use frame_support::{ assert_ok, derive_impl, parameter_types, traits::{Everything, Hooks}, diff --git a/pallets/collective/src/benchmarking.rs b/pallets/collective/src/benchmarking.rs index cf44e9948..380a78395 100644 --- a/pallets/collective/src/benchmarking.rs +++ b/pallets/collective/src/benchmarking.rs @@ -16,6 +16,7 @@ // limitations under the License. //! Staking pallet benchmarking. +#![allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] use super::*; use crate::Pallet as Collective; @@ -70,7 +71,7 @@ benchmarks_instance_pallet! { // Proposals should be different so that different proposal hashes are generated let proposal: T::Proposal = SystemCall::::remark { remark: id_to_remark_data(i, length) }.into(); Collective::::propose( - SystemOrigin::Signed(old_members.last().unwrap().clone()).into(), + SystemOrigin::Signed(old_members.last().expect("m is greater than 0; old_members must have at least 1 element; qed").clone()).into(), Box::new(proposal.clone()), MAX_BYTES, TryInto::>::try_into(3u64).ok().expect("convert u64 to block number.") diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index c1bcad3e7..3be6529e0 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -54,7 +54,7 @@ use frame_support::{ use scale_info::TypeInfo; use sp_io::storage; use sp_runtime::traits::Dispatchable; -use sp_runtime::{traits::Hash, RuntimeDebug}; +use sp_runtime::{traits::Hash, RuntimeDebug, Saturating}; use sp_std::{marker::PhantomData, prelude::*, result}; #[cfg(test)] @@ -119,7 +119,7 @@ impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote { _no_votes: MemberCount, len: MemberCount, ) -> bool { - let more_than_majority = yes_votes * 2 > len; + let more_than_majority = yes_votes.saturating_mul(2) > len; more_than_majority || prime_vote.unwrap_or(false) } } @@ -545,7 +545,9 @@ pub mod pallet { Error::::DurationLowerThanConfiguredMotionDuration ); - let threshold = (T::GetVotingMembers::get_count() / 2) + 1; + let threshold = T::GetVotingMembers::get_count() + .saturating_div(2) + .saturating_add(1); let members = Self::members(); let (proposal_len, active_proposals) = @@ -716,10 +718,15 @@ impl, I: 'static> Pallet { })?; let index = Self::proposal_count(); - >::mutate(|i| *i += 1); + >::try_mutate(|i| { + *i = i + .checked_add(1) + .ok_or(Error::::TooManyActiveProposals)?; + Ok::<(), Error>(()) + })?; >::insert(proposal_hash, proposal); let votes = { - let end = frame_system::Pallet::::block_number() + duration; + let end = frame_system::Pallet::::block_number().saturating_add(duration); Votes { index, threshold, @@ -862,10 +869,10 @@ impl, I: 'static> Pallet { // default voting strategy. let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats); - let abstentions = seats - (yes_votes + no_votes); + let abstentions = seats.saturating_sub(yes_votes.saturating_add(no_votes)); match default { - true => yes_votes += abstentions, - false => no_votes += abstentions, + true => yes_votes = yes_votes.saturating_add(abstentions), + false => no_votes = no_votes.saturating_add(abstentions), } let approved = yes_votes >= voting.threshold; @@ -981,7 +988,7 @@ impl, I: 'static> Pallet { Voting::::remove(proposal_hash); let num_proposals = Proposals::::mutate(|proposals| { proposals.retain(|h| h != &proposal_hash); - proposals.len() + 1 // calculate weight based on original length + proposals.len().saturating_add(1) // calculate weight based on original length }); num_proposals as u32 } @@ -1154,7 +1161,7 @@ impl< type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n * D > N * m => Ok(()), + RawOrigin::Members(n, m) if n.saturating_mul(D) > N.saturating_mul(m) => Ok(()), r => Err(O::from(r)), }) } @@ -1179,7 +1186,7 @@ impl< type Success = (); fn try_origin(o: O) -> Result { o.into().and_then(|o| match o { - RawOrigin::Members(n, m) if n * D >= N * m => Ok(()), + RawOrigin::Members(n, m) if n.saturating_mul(D) >= N.saturating_mul(m) => Ok(()), r => Err(O::from(r)), }) } diff --git a/pallets/collective/src/tests.rs b/pallets/collective/src/tests.rs index 672556edb..91fca58d4 100644 --- a/pallets/collective/src/tests.rs +++ b/pallets/collective/src/tests.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(non_camel_case_types)] +#![allow(non_camel_case_types, clippy::indexing_slicing, clippy::unwrap_used)] use super::{Event as CollectiveEvent, *}; use crate as pallet_collective; diff --git a/pallets/commitments/src/benchmarking.rs b/pallets/commitments/src/benchmarking.rs index 1b877a8ce..54247bb9d 100644 --- a/pallets/commitments/src/benchmarking.rs +++ b/pallets/commitments/src/benchmarking.rs @@ -1,5 +1,6 @@ //! Benchmarking setup #![cfg(feature = "runtime-benchmarks")] +#![allow(clippy::arithmetic_side_effects)] use super::*; #[allow(unused)] @@ -17,7 +18,11 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { // This creates an `IdentityInfo` object with `num_fields` extra fields. // All data is pre-populated with some arbitrary bytes. fn create_identity_info(_num_fields: u32) -> CommitmentInfo { - let _data = Data::Raw(vec![0; 32].try_into().unwrap()); + let _data = Data::Raw( + vec![0; 32] + .try_into() + .expect("vec length is less than 64; qed"), + ); CommitmentInfo { fields: Default::default(), diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index dec9177d2..81802b64a 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -12,7 +12,7 @@ pub use types::*; pub use weights::WeightInfo; use frame_support::traits::Currency; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Zero, Saturating}; use sp_std::boxed::Box; type BalanceOf = @@ -136,12 +136,12 @@ pub mod pallet { let cur_block = >::block_number(); if let Some(last_commit) = >::get(netuid, &who) { ensure!( - cur_block >= last_commit + T::RateLimit::get(), + cur_block >= last_commit.saturating_add(T::RateLimit::get()), Error::::CommitmentSetRateLimitExceeded ); } - let fd = >::from(extra_fields) * T::FieldDeposit::get(); + let fd = >::from(extra_fields).saturating_mul(T::FieldDeposit::get()); let mut id = match >::get(netuid, &who) { Some(mut id) => { id.info = *info; @@ -156,12 +156,13 @@ pub mod pallet { }; let old_deposit = id.deposit; - id.deposit = T::InitialDeposit::get() + fd; + id.deposit = T::InitialDeposit::get().saturating_add(fd); if id.deposit > old_deposit { - T::Currency::reserve(&who, id.deposit - old_deposit)?; + T::Currency::reserve(&who, id.deposit.saturating_sub(old_deposit))?; } if old_deposit > id.deposit { - let err_amount = T::Currency::unreserve(&who, old_deposit - id.deposit); + let err_amount = + T::Currency::unreserve(&who, old_deposit.saturating_sub(id.deposit)); debug_assert!(err_amount.is_zero()); } diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 6ca9ee603..5a1d0bd64 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -66,7 +66,7 @@ impl Decode for Data { Ok(match b { 0 => Data::None, n @ 1..=129 => { - let mut r: BoundedVec<_, _> = vec![0u8; n as usize - 1] + let mut r: BoundedVec<_, _> = vec![0u8; (n as usize).saturating_sub(1)] .try_into() .expect("bound checked in match arm condition; qed"); input.read(&mut r[..])?; @@ -86,8 +86,8 @@ impl Encode for Data { match self { Data::None => vec![0u8; 1], Data::Raw(ref x) => { - let l = x.len().min(128); - let mut r = vec![l as u8 + 1]; + let l = x.len().min(128) as u8; + let mut r = vec![l.saturating_add(1)]; r.extend_from_slice(&x[..]); r } @@ -344,6 +344,7 @@ impl< } #[cfg(test)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; diff --git a/pallets/registry/src/benchmarking.rs b/pallets/registry/src/benchmarking.rs index bb2c4ac06..ee2088478 100644 --- a/pallets/registry/src/benchmarking.rs +++ b/pallets/registry/src/benchmarking.rs @@ -1,5 +1,6 @@ //! Benchmarking setup #![cfg(feature = "runtime-benchmarks")] +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] use super::*; #[allow(unused)] @@ -19,7 +20,11 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { // This creates an `IdentityInfo` object with `num_fields` extra fields. // All data is pre-populated with some arbitrary bytes. fn create_identity_info(_num_fields: u32) -> IdentityInfo { - let data = Data::Raw(vec![0; 32].try_into().unwrap()); + let data = Data::Raw( + vec![0; 32] + .try_into() + .expect("size does not exceed 64; qed"), + ); IdentityInfo { additional: Default::default(), diff --git a/pallets/registry/src/lib.rs b/pallets/registry/src/lib.rs index e32b1fc5f..bb1518768 100644 --- a/pallets/registry/src/lib.rs +++ b/pallets/registry/src/lib.rs @@ -15,7 +15,7 @@ use frame_support::traits::tokens::{ fungible::{self, MutateHold as _}, Precision, }; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Zero, Saturating}; use sp_std::boxed::Box; type BalanceOf = @@ -132,7 +132,7 @@ pub mod pallet { Error::::TooManyFieldsInIdentityInfo ); - let fd = >::from(extra_fields) * T::FieldDeposit::get(); + let fd = >::from(extra_fields).saturating_mul(T::FieldDeposit::get()); let mut id = match >::get(&identified) { Some(mut id) => { id.info = *info; @@ -145,23 +145,24 @@ pub mod pallet { }; let old_deposit = id.deposit; - id.deposit = T::InitialDeposit::get() + fd; + id.deposit = T::InitialDeposit::get().saturating_add(fd); if id.deposit > old_deposit { T::Currency::hold( &HoldReason::RegistryIdentity.into(), &who, - id.deposit - old_deposit, + id.deposit.saturating_sub(old_deposit), )?; } if old_deposit > id.deposit { let release_res = T::Currency::release( &HoldReason::RegistryIdentity.into(), &who, - old_deposit - id.deposit, + old_deposit.saturating_sub(id.deposit), Precision::BestEffort, ); - debug_assert!(release_res - .is_ok_and(|released_amount| released_amount == (old_deposit - id.deposit))); + debug_assert!(release_res.is_ok_and( + |released_amount| released_amount == old_deposit.saturating_sub(id.deposit) + )); } >::insert(&identified, id); diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index 0573392cd..12ee857d2 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -67,7 +67,7 @@ impl Decode for Data { Ok(match b { 0 => Data::None, n @ 1..=65 => { - let mut r: BoundedVec<_, _> = vec![0u8; n as usize - 1] + let mut r: BoundedVec<_, _> = vec![0u8; (n as usize).saturating_sub(1)] .try_into() .expect("bound checked in match arm condition; qed"); input.read(&mut r[..])?; @@ -87,8 +87,8 @@ impl Encode for Data { match self { Data::None => vec![0u8; 1], Data::Raw(ref x) => { - let l = x.len().min(64); - let mut r = vec![l as u8 + 1]; + let l = x.len().min(64) as u8; + let mut r = vec![l.saturating_add(1)]; r.extend_from_slice(&x[..]); r } @@ -403,6 +403,7 @@ impl< } #[cfg(test)] +#[allow(clippy::indexing_slicing, clippy::unwrap_used)] mod tests { use super::*; diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index a7dd29fbb..403ba413e 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -1,5 +1,5 @@ //! Subtensor pallet benchmarking. - +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] #![cfg(feature = "runtime-benchmarks")] use crate::Pallet as Subtensor; diff --git a/pallets/subtensor/src/block_step.rs b/pallets/subtensor/src/block_step.rs index 80733e6b7..cdd470cd0 100644 --- a/pallets/subtensor/src/block_step.rs +++ b/pallets/subtensor/src/block_step.rs @@ -1,6 +1,7 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; use frame_support::storage::IterableStorageMap; +use sp_runtime::Saturating; use substrate_fixed::types::I110F18; use substrate_fixed::types::I64F64; use substrate_fixed::types::I96F32; @@ -27,6 +28,7 @@ impl Pallet { Ok(()) } + #[allow(clippy::arithmetic_side_effects)] /// Helper function which returns the number of blocks remaining before we will run the epoch on this /// network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 /// @@ -42,9 +44,13 @@ impl Pallet { if tempo == 0 { return 1000; } - tempo as u64 - (block_number + netuid as u64 + 1) % (tempo as u64 + 1) + (tempo as u64).saturating_sub( + block_number.saturating_add(netuid as u64).saturating_add(1) + % (tempo as u64).saturating_add(1), + ) } + #[allow(clippy::arithmetic_side_effects)] /// Helper function returns the number of tuples to drain on a particular step based on /// the remaining tuples to sink and the block number /// @@ -55,18 +61,20 @@ impl Pallet { n_remaining: usize, ) -> usize { let blocks_until_epoch: u64 = Self::blocks_until_next_epoch(netuid, tempo, block_number); - if blocks_until_epoch / 2 == 0 { + if blocks_until_epoch.saturating_div(2) == 0 { return n_remaining; } // drain all. - if tempo / 2 == 0 { + if tempo.saturating_div(2) == 0 { return n_remaining; } // drain all if n_remaining == 0 { return 0; } // nothing to drain at all. // Else return enough tuples to drain all within half the epoch length. - let to_sink_via_tempo: usize = n_remaining / (tempo as usize / 2); - let to_sink_via_blocks_until_epoch: usize = n_remaining / (blocks_until_epoch as usize / 2); + let to_sink_via_tempo: usize = + n_remaining.saturating_div((tempo as usize).saturating_div(2)); + let to_sink_via_blocks_until_epoch: usize = + n_remaining.saturating_div((blocks_until_epoch as usize).saturating_div(2)); if to_sink_via_tempo > to_sink_via_blocks_until_epoch { to_sink_via_tempo } else { @@ -95,7 +103,7 @@ impl Pallet { *server_amount, *validator_amount, ); - total_emitted += *server_amount + *validator_amount; + total_emitted.saturating_accrue((*server_amount).saturating_add(*validator_amount)); } LoadedEmission::::remove(netuid); TotalIssuance::::put(TotalIssuance::::get().saturating_add(total_emitted)); @@ -142,7 +150,9 @@ impl Pallet { Self::coinbase(cut.to_num::()); } // --- 5. Add remaining amount to the network's pending emission. - PendingEmission::::mutate(netuid, |queued| *queued += remaining.to_num::()); + PendingEmission::::mutate(netuid, |queued| { + queued.saturating_accrue(remaining.to_num::()) + }); log::debug!( "netuid_i: {:?} queued_emission: +{:?} ", netuid, @@ -154,7 +164,7 @@ impl Pallet { // --- 3.1 No epoch, increase blocks since last step and continue, Self::set_blocks_since_last_step( netuid, - Self::get_blocks_since_last_step(netuid) + 1, + Self::get_blocks_since_last_step(netuid).saturating_add(1), ); continue; } @@ -176,7 +186,7 @@ impl Pallet { // --- 9. Check that the emission does not exceed the allowed total. let emission_sum: u128 = emission_tuples_this_block .iter() - .map(|(_account_id, ve, se)| *ve as u128 + *se as u128) + .map(|(_account_id, ve, se)| (*ve as u128).saturating_add(*se as u128)) .sum(); if emission_sum > emission_to_drain as u128 { continue; @@ -208,7 +218,10 @@ impl Pallet { // --- 1. Check if the hotkey is a delegate. If not, we simply pass the stake through to the // coldkey - hotkey account as normal. if !Self::hotkey_is_delegate(hotkey) { - Self::increase_stake_on_hotkey_account(hotkey, server_emission + validator_emission); + Self::increase_stake_on_hotkey_account( + hotkey, + server_emission.saturating_add(validator_emission), + ); return; } // Then this is a delegate, we distribute validator_emission, then server_emission. @@ -218,7 +231,7 @@ impl Pallet { let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); let delegate_take: u64 = Self::calculate_delegate_proportional_take(hotkey, validator_emission); - let validator_emission_minus_take: u64 = validator_emission - delegate_take; + let validator_emission_minus_take: u64 = validator_emission.saturating_sub(delegate_take); let mut remaining_validator_emission: u64 = validator_emission_minus_take; // 3. -- The remaining emission goes to the owners in proportion to the stake delegated. @@ -244,14 +257,14 @@ impl Pallet { hotkey, stake_proportion ); - remaining_validator_emission -= stake_proportion; + remaining_validator_emission.saturating_reduce(stake_proportion); } // --- 5. Last increase final account balance of delegate after 4, since 5 will change the stake proportion of // the delegate and effect calculation in 4. Self::increase_stake_on_hotkey_account( hotkey, - delegate_take + remaining_validator_emission, + delegate_take.saturating_add(remaining_validator_emission), ); log::debug!("delkey: {:?} delegate_take: +{:?} ", hotkey, delegate_take); // Also emit the server_emission to the hotkey @@ -311,8 +324,10 @@ impl Pallet { if total_stake == 0 { return 0; }; - let stake_proportion: I64F64 = I64F64::from_num(stake) / I64F64::from_num(total_stake); - let proportional_emission: I64F64 = I64F64::from_num(emission) * stake_proportion; + let stake_proportion: I64F64 = + I64F64::from_num(stake).saturating_div(I64F64::from_num(total_stake)); + let proportional_emission: I64F64 = + I64F64::from_num(emission).saturating_mul(stake_proportion); proportional_emission.to_num::() } @@ -320,9 +335,9 @@ impl Pallet { /// pub fn calculate_delegate_proportional_take(hotkey: &T::AccountId, emission: u64) -> u64 { if Self::hotkey_is_delegate(hotkey) { - let take_proportion: I64F64 = - I64F64::from_num(Delegates::::get(hotkey)) / I64F64::from_num(u16::MAX); - let take_emission: I64F64 = take_proportion * I64F64::from_num(emission); + let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) + .saturating_div(I64F64::from_num(u16::MAX)); + let take_emission: I64F64 = take_proportion.saturating_mul(I64F64::from_num(emission)); take_emission.to_num::() } else { 0 @@ -349,7 +364,7 @@ impl Pallet { // --- 3. Check if we are at the adjustment interval for this network. // If so, we need to adjust the registration difficulty based on target and actual registrations. - if (current_block - last_adjustment_block) >= adjustment_interval as u64 { + if current_block.saturating_sub(last_adjustment_block) >= adjustment_interval as u64 { log::debug!("interval reached."); // --- 4. Get the current counters for this network w.r.t burn and difficulty values. @@ -496,14 +511,21 @@ impl Pallet { target_registrations_per_interval: u16, ) -> u64 { let updated_difficulty: I110F18 = I110F18::from_num(current_difficulty) - * I110F18::from_num(registrations_this_interval + target_registrations_per_interval) - / I110F18::from_num( - target_registrations_per_interval + target_registrations_per_interval, + .saturating_mul(I110F18::from_num( + registrations_this_interval.saturating_add(target_registrations_per_interval), + )) + .saturating_div(I110F18::from_num( + target_registrations_per_interval.saturating_add(target_registrations_per_interval), + )); + let alpha: I110F18 = I110F18::from_num(Self::get_adjustment_alpha(netuid)) + .saturating_div(I110F18::from_num(u64::MAX)); + let next_value: I110F18 = alpha + .saturating_mul(I110F18::from_num(current_difficulty)) + .saturating_add( + I110F18::from_num(1.0) + .saturating_sub(alpha) + .saturating_mul(updated_difficulty), ); - let alpha: I110F18 = - I110F18::from_num(Self::get_adjustment_alpha(netuid)) / I110F18::from_num(u64::MAX); - let next_value: I110F18 = alpha * I110F18::from_num(current_difficulty) - + (I110F18::from_num(1.0) - alpha) * updated_difficulty; if next_value >= I110F18::from_num(Self::get_max_difficulty(netuid)) { Self::get_max_difficulty(netuid) } else if next_value <= I110F18::from_num(Self::get_min_difficulty(netuid)) { @@ -523,14 +545,21 @@ impl Pallet { target_registrations_per_interval: u16, ) -> u64 { let updated_burn: I110F18 = I110F18::from_num(current_burn) - * I110F18::from_num(registrations_this_interval + target_registrations_per_interval) - / I110F18::from_num( - target_registrations_per_interval + target_registrations_per_interval, + .saturating_mul(I110F18::from_num( + registrations_this_interval.saturating_add(target_registrations_per_interval), + )) + .saturating_div(I110F18::from_num( + target_registrations_per_interval.saturating_add(target_registrations_per_interval), + )); + let alpha: I110F18 = I110F18::from_num(Self::get_adjustment_alpha(netuid)) + .saturating_div(I110F18::from_num(u64::MAX)); + let next_value: I110F18 = alpha + .saturating_mul(I110F18::from_num(current_burn)) + .saturating_add( + I110F18::from_num(1.0) + .saturating_sub(alpha) + .saturating_mul(updated_burn), ); - let alpha: I110F18 = - I110F18::from_num(Self::get_adjustment_alpha(netuid)) / I110F18::from_num(u64::MAX); - let next_value: I110F18 = alpha * I110F18::from_num(current_burn) - + (I110F18::from_num(1.0) - alpha) * updated_burn; if next_value >= I110F18::from_num(Self::get_max_burn_as_u64(netuid)) { Self::get_max_burn_as_u64(netuid) } else if next_value <= I110F18::from_num(Self::get_min_burn_as_u64(netuid)) { diff --git a/pallets/subtensor/src/delegate_info.rs b/pallets/subtensor/src/delegate_info.rs index b33415a3b..1f8b06b69 100644 --- a/pallets/subtensor/src/delegate_info.rs +++ b/pallets/subtensor/src/delegate_info.rs @@ -52,8 +52,9 @@ impl Pallet { let emission: U64F64 = Self::get_emission_for_uid(*netuid, uid).into(); let tempo: U64F64 = Self::get_tempo(*netuid).into(); - let epochs_per_day: U64F64 = U64F64::from_num(7200) / tempo; - emissions_per_day += emission * epochs_per_day; + let epochs_per_day: U64F64 = U64F64::from_num(7200).saturating_div(tempo); + emissions_per_day = + emissions_per_day.saturating_add(emission.saturating_mul(epochs_per_day)); } } @@ -65,8 +66,9 @@ impl Pallet { let mut return_per_1000: U64F64 = U64F64::from_num(0); if total_stake > U64F64::from_num(0) { - return_per_1000 = (emissions_per_day * U64F64::from_num(0.82)) - / (total_stake / U64F64::from_num(1000)); + return_per_1000 = emissions_per_day + .saturating_mul(U64F64::from_num(0.82)) + .saturating_div(total_stake.saturating_div(U64F64::from_num(1000))); } return DelegateInfo { diff --git a/pallets/subtensor/src/epoch.rs b/pallets/subtensor/src/epoch.rs index cc146dd59..cd2666d9d 100644 --- a/pallets/subtensor/src/epoch.rs +++ b/pallets/subtensor/src/epoch.rs @@ -32,7 +32,7 @@ impl Pallet { // Inactive mask. let inactive: Vec = last_update .iter() - .map(|updated| *updated + activity_cutoff < current_block) + .map(|updated| updated.saturating_add(activity_cutoff) < current_block) .collect(); log::trace!("Inactive:\n{:?}\n", inactive.clone()); @@ -175,9 +175,10 @@ impl Pallet { // log::trace!( "ΔB:\n{:?}\n", &bonds_delta ); // Compute bonds moving average. - let bonds_moving_average: I64F64 = - I64F64::from_num(Self::get_bonds_moving_average(netuid)) / I64F64::from_num(1_000_000); - let alpha: I32F32 = I32F32::from_num(1) - I32F32::from_num(bonds_moving_average); + let bonds_moving_average: I64F64 = I64F64::from_num(Self::get_bonds_moving_average(netuid)) + .saturating_div(I64F64::from_num(1_000_000)); + let alpha: I32F32 = + I32F32::from_num(1).saturating_sub(I32F32::from_num(bonds_moving_average)); let mut ema_bonds: Vec> = mat_ema(&bonds_delta, &bonds, alpha); inplace_col_normalize(&mut ema_bonds); // sum_i b_ij = 1 // log::trace!( "emaB:\n{:?}\n", &ema_bonds ); @@ -198,7 +199,7 @@ impl Pallet { let combined_emission: Vec = incentive .iter() .zip(dividends.clone()) - .map(|(ii, di)| ii + di) + .map(|(ii, di)| ii.saturating_add(di)) .collect(); let emission_sum: I32F32 = combined_emission.iter().sum(); @@ -228,7 +229,7 @@ impl Pallet { let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se) * float_rao_emission) + .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) .collect(); let server_emission: Vec = server_emission .iter() @@ -237,7 +238,7 @@ impl Pallet { let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve) * float_rao_emission) + .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) .collect(); let validator_emission: Vec = validator_emission .iter() @@ -247,7 +248,7 @@ impl Pallet { // Used only to track combined emission in the storage. let combined_emission: Vec = normalized_combined_emission .iter() - .map(|ce: &I32F32| I96F32::from_num(*ce) * float_rao_emission) + .map(|ce: &I32F32| I96F32::from_num(*ce).saturating_mul(float_rao_emission)) .collect(); let combined_emission: Vec = combined_emission .iter() @@ -376,7 +377,7 @@ impl Pallet { // Inactive mask. let inactive: Vec = last_update .iter() - .map(|updated| *updated + activity_cutoff < current_block) + .map(|updated| updated.saturating_add(activity_cutoff) < current_block) .collect(); log::trace!("Inactive: {:?}", inactive.clone()); @@ -535,9 +536,10 @@ impl Pallet { // log::trace!( "ΔB (norm): {:?}", &bonds_delta ); // Compute bonds moving average. - let bonds_moving_average: I64F64 = - I64F64::from_num(Self::get_bonds_moving_average(netuid)) / I64F64::from_num(1_000_000); - let alpha: I32F32 = I32F32::from_num(1) - I32F32::from_num(bonds_moving_average); + let bonds_moving_average: I64F64 = I64F64::from_num(Self::get_bonds_moving_average(netuid)) + .saturating_div(I64F64::from_num(1_000_000)); + let alpha: I32F32 = + I32F32::from_num(1).saturating_sub(I32F32::from_num(bonds_moving_average)); let mut ema_bonds: Vec> = mat_ema_sparse(&bonds_delta, &bonds, alpha); // Normalize EMA bonds. @@ -558,7 +560,7 @@ impl Pallet { let combined_emission: Vec = incentive .iter() .zip(dividends.clone()) - .map(|(ii, di)| ii + di) + .map(|(ii, di)| ii.saturating_add(di)) .collect(); let emission_sum: I32F32 = combined_emission.iter().sum(); @@ -588,7 +590,7 @@ impl Pallet { let server_emission: Vec = normalized_server_emission .iter() - .map(|se: &I32F32| I96F32::from_num(*se) * float_rao_emission) + .map(|se: &I32F32| I96F32::from_num(*se).saturating_mul(float_rao_emission)) .collect(); let server_emission: Vec = server_emission .iter() @@ -597,7 +599,7 @@ impl Pallet { let validator_emission: Vec = normalized_validator_emission .iter() - .map(|ve: &I32F32| I96F32::from_num(*ve) * float_rao_emission) + .map(|ve: &I32F32| I96F32::from_num(*ve).saturating_mul(float_rao_emission)) .collect(); let validator_emission: Vec = validator_emission .iter() @@ -607,7 +609,7 @@ impl Pallet { // Only used to track emission in storage. let combined_emission: Vec = normalized_combined_emission .iter() - .map(|ce: &I32F32| I96F32::from_num(*ce) * float_rao_emission) + .map(|ce: &I32F32| I96F32::from_num(*ce).saturating_mul(float_rao_emission)) .collect(); let combined_emission: Vec = combined_emission .iter() @@ -704,7 +706,7 @@ impl Pallet { I32F32::from_num(Self::get_rho(netuid)) } pub fn get_float_kappa(netuid: u16) -> I32F32 { - I32F32::from_num(Self::get_kappa(netuid)) / I32F32::from_num(u16::MAX) + I32F32::from_num(Self::get_kappa(netuid)).saturating_div(I32F32::from_num(u16::MAX)) } pub fn get_normalized_stake(netuid: u16) -> Vec { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b590781f5..ecb02bbeb 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1144,7 +1144,7 @@ pub mod pallet { // Set max allowed uids MaxAllowedUids::::insert(netuid, max_uids); - let mut next_uid = 0; + let mut next_uid = 0u16; for (coldkey, hotkeys) in self.stakes.iter() { for (hotkey, stake_uid) in hotkeys.iter() { @@ -1183,7 +1183,9 @@ pub mod pallet { Stake::::insert(hotkey.clone(), coldkey.clone(), stake); - next_uid += 1; + next_uid = next_uid.checked_add(1).expect( + "should not have total number of hotkey accounts larger than u16::MAX", + ); } } @@ -1191,7 +1193,11 @@ pub mod pallet { SubnetworkN::::insert(netuid, next_uid); // --- Increase total network count. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| { + *n = n.checked_add(1).expect( + "should not have total number of networks larger than u16::MAX in genesis", + ) + }); // Get the root network uid. let root_netuid: u16 = 0; @@ -1200,7 +1206,11 @@ pub mod pallet { NetworksAdded::::insert(root_netuid, true); // Increment the number of total networks. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| { + *n = n.checked_add(1).expect( + "should not have total number of networks larger than u16::MAX in genesis", + ) + }); // Set the number of validators to 1. SubnetworkN::::insert(root_netuid, 0); @@ -1213,7 +1223,7 @@ pub mod pallet { // Set the min allowed weights to zero, no weights restrictions. MinAllowedWeights::::insert(root_netuid, 0); - // Set the max weight limit to infitiy, no weight restrictions. + // Set the max weight limit to infinity, no weight restrictions. MaxWeightsLimit::::insert(root_netuid, u16::MAX); // Add default root tempo. @@ -2061,8 +2071,8 @@ pub mod pallet { let _stake = Self::get_total_stake_for_hotkey(hotkey); let current_block_number: u64 = Self::get_current_block_as_u64(); let default_priority: u64 = - current_block_number - Self::get_last_update_for_uid(netuid, uid); - return default_priority + u32::MAX as u64; + current_block_number.saturating_sub(Self::get_last_update_for_uid(netuid, uid)); + return default_priority.saturating_add(u32::MAX as u64); } 0 } @@ -2090,7 +2100,7 @@ pub mod pallet { return false; } if Self::get_registrations_this_interval(netuid) - >= Self::get_target_registrations_per_interval(netuid) * 3 + >= Self::get_target_registrations_per_interval(netuid).saturating_mul(3) { return false; } @@ -2243,7 +2253,8 @@ where Pallet::::get_registrations_this_interval(*netuid); let max_registrations_per_interval = Pallet::::get_target_registrations_per_interval(*netuid); - if registrations_this_interval >= (max_registrations_per_interval * 3) { + if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) + { // If the registration limit for the interval is exceeded, reject the transaction return InvalidTransaction::ExhaustsResources.into(); } diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/math.rs index e10cc0001..f7784ea1c 100644 --- a/pallets/subtensor/src/math.rs +++ b/pallets/subtensor/src/math.rs @@ -1,4 +1,4 @@ -use sp_runtime::traits::CheckedAdd; +use sp_runtime::{traits::CheckedAdd, Saturating}; use sp_std::vec; use substrate_fixed::transcendental::exp; use substrate_fixed::types::{I32F32, I64F64}; @@ -44,12 +44,12 @@ pub fn u16_to_fixed(x: u16) -> I32F32 { #[allow(dead_code)] pub fn u16_proportion_to_fixed(x: u16) -> I32F32 { - I32F32::from_num(x) / I32F32::from_num(u16::MAX) + I32F32::from_num(x).saturating_div(I32F32::from_num(u16::MAX)) } #[allow(dead_code)] pub fn fixed_proportion_to_u16(x: I32F32) -> u16 { - fixed_to_u16(x * I32F32::from_num(u16::MAX)) + fixed_to_u16(x.saturating_mul(I32F32::from_num(u16::MAX))) } #[allow(dead_code)] @@ -93,25 +93,38 @@ pub fn vec_max_upscale_to_u16(vec: &[I32F32]) -> Vec { if *val == I32F32::from_num(0) { return vec .iter() - .map(|e: &I32F32| (e * u16_max).to_num::()) + .map(|e: &I32F32| e.saturating_mul(u16_max).to_num::()) .collect(); } if *val > threshold { return vec .iter() - .map(|e: &I32F32| (e * (u16_max / *val)).round().to_num::()) + .map(|e: &I32F32| { + e.saturating_mul(u16_max.saturating_div(*val)) + .round() + .to_num::() + }) .collect(); } return vec .iter() - .map(|e: &I32F32| ((e * u16_max) / *val).round().to_num::()) + .map(|e: &I32F32| { + e.saturating_mul(u16_max) + .saturating_div(*val) + .round() + .to_num::() + }) .collect(); } None => { let sum: I32F32 = vec.iter().sum(); return vec .iter() - .map(|e: &I32F32| ((e * u16_max) / sum).to_num::()) + .map(|e: &I32F32| { + e.saturating_mul(u16_max) + .saturating_div(sum) + .to_num::() + }) .collect(); } } @@ -127,7 +140,8 @@ pub fn vec_u16_max_upscale_to_u16(vec: &[u16]) -> Vec { #[allow(dead_code)] // Checks if u16 vector, when normalized, has a max value not greater than a u16 ratio max_limit. pub fn check_vec_max_limited(vec: &[u16], max_limit: u16) -> bool { - let max_limit_fixed: I32F32 = I32F32::from_num(max_limit) / I32F32::from_num(u16::MAX); + let max_limit_fixed: I32F32 = + I32F32::from_num(max_limit).saturating_div(I32F32::from_num(u16::MAX)); let mut vec_fixed: Vec = vec.iter().map(|e: &u16| I32F32::from_num(*e)).collect(); inplace_normalize(&mut vec_fixed); let max_value: Option<&I32F32> = vec_fixed.iter().max(); @@ -196,7 +210,7 @@ pub fn exp_safe(input: I32F32) -> I32F32 { pub fn sigmoid_safe(input: I32F32, rho: I32F32, kappa: I32F32) -> I32F32 { let one: I32F32 = I32F32::from_num(1); let offset: I32F32 = input.saturating_sub(kappa); // (input - kappa) - let neg_rho: I32F32 = rho.saturating_mul(-one); // -rho + let neg_rho: I32F32 = rho.saturating_mul(one.saturating_neg()); // -rho let exp_input: I32F32 = neg_rho.saturating_mul(offset); // -rho*(input-kappa) let exp_output: I32F32 = exp_safe(exp_input); // exp(-rho*(input-kappa)) let denominator: I32F32 = exp_output.saturating_add(one); // 1 + exp(-rho*(input-kappa)) @@ -214,7 +228,7 @@ pub fn is_topk(vector: &[I32F32], k: usize) -> Vec { } let mut idxs: Vec = (0..n).collect(); idxs.sort_by_key(|&idx| &vector[idx]); // ascending stable sort - for &idx in idxs.iter().take(n - k) { + for &idx in idxs.iter().take(n.saturating_sub(k)) { result[idx] = false; } result @@ -225,7 +239,7 @@ pub fn is_topk(vector: &[I32F32], k: usize) -> Vec { pub fn normalize(x: &[I32F32]) -> Vec { let x_sum: I32F32 = sum(x); if x_sum != I32F32::from_num(0.0_f32) { - return x.iter().map(|xi| xi / x_sum).collect(); + return x.iter().map(|xi| xi.saturating_div(x_sum)).collect(); } else { x.to_vec() } @@ -238,7 +252,8 @@ pub fn inplace_normalize(x: &mut [I32F32]) { if x_sum == I32F32::from_num(0.0_f32) { return; } - x.iter_mut().for_each(|value| *value /= x_sum); + x.into_iter() + .for_each(|value| *value = value.saturating_div(x_sum)); } // Normalizes (sum to 1 except 0) the input vector directly in-place, using the sum arg. @@ -247,7 +262,8 @@ pub fn inplace_normalize_using_sum(x: &mut [I32F32], x_sum: I32F32) { if x_sum == I32F32::from_num(0.0_f32) { return; } - x.iter_mut().for_each(|value| *value /= x_sum); + x.into_iter() + .for_each(|value| *value = value.saturating_div(x_sum)); } // Normalizes (sum to 1 except 0) the I64F64 input vector directly in-place. @@ -257,7 +273,8 @@ pub fn inplace_normalize_64(x: &mut [I64F64]) { if x_sum == I64F64::from_num(0) { return; } - x.iter_mut().for_each(|value| *value /= x_sum); + x.into_iter() + .for_each(|value| *value = value.saturating_div(x_sum)); } /// Normalizes (sum to 1 except 0) each row (dim=0) of a I64F64 matrix in-place. @@ -267,7 +284,7 @@ pub fn inplace_row_normalize_64(x: &mut [Vec]) { let row_sum: I64F64 = row.iter().sum(); if row_sum > I64F64::from_num(0.0_f64) { row.iter_mut() - .for_each(|x_ij: &mut I64F64| *x_ij /= row_sum); + .for_each(|x_ij: &mut I64F64| *x_ij = x_ij.saturating_div(row_sum)); } } } @@ -280,7 +297,7 @@ pub fn vecdiv(x: &[I32F32], y: &[I32F32]) -> Vec { .zip(y) .map(|(x_i, y_i)| { if *y_i != 0 { - x_i / y_i + x_i.saturating_div(*y_i) } else { I32F32::from_num(0) } @@ -294,8 +311,8 @@ pub fn inplace_row_normalize(x: &mut [Vec]) { for row in x { let row_sum: I32F32 = row.iter().sum(); if row_sum > I32F32::from_num(0.0_f32) { - row.iter_mut() - .for_each(|x_ij: &mut I32F32| *x_ij /= row_sum); + row.into_iter() + .for_each(|x_ij: &mut I32F32| *x_ij = x_ij.saturating_div(row_sum)); } } } @@ -308,7 +325,7 @@ pub fn inplace_row_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>]) { if row_sum > I32F32::from_num(0.0) { sparse_row .iter_mut() - .for_each(|(_j, value)| *value /= row_sum); + .for_each(|(_j, value)| *value = value.saturating_div(row_sum)); } } } @@ -347,7 +364,7 @@ pub fn col_sum(x: &[Vec]) -> Vec { .fold(vec![I32F32::from_num(0); cols], |acc, next_row| { acc.into_iter() .zip(next_row) - .map(|(acc_elem, next_elem)| acc_elem + next_elem) + .map(|(acc_elem, next_elem)| acc_elem.saturating_add(*next_elem)) .collect() }) } @@ -358,7 +375,7 @@ pub fn col_sum_sparse(sparse_matrix: &[Vec<(u16, I32F32)>], columns: u16) -> Vec let mut result: Vec = vec![I32F32::from_num(0); columns as usize]; for sparse_row in sparse_matrix { for (j, value) in sparse_row { - result[*j as usize] += value; + result[*j as usize] = result[*j as usize].saturating_add(*value); } } result @@ -370,7 +387,7 @@ pub fn inplace_col_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], co let mut col_sum: Vec = vec![I32F32::from_num(0.0); columns as usize]; // assume square matrix, rows=cols for sparse_row in sparse_matrix.iter() { for (j, value) in sparse_row.iter() { - col_sum[*j as usize] += value; + col_sum[*j as usize] = col_sum[*j as usize].saturating_add(*value); } } for sparse_row in sparse_matrix { @@ -378,7 +395,7 @@ pub fn inplace_col_normalize_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], co if col_sum[*j as usize] == I32F32::from_num(0.0_f32) { continue; } - *value /= col_sum[*j as usize]; + *value = value.saturating_div(col_sum[*j as usize]); } } } @@ -398,7 +415,7 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { .fold(vec![I32F32::from_num(0.0); cols], |acc, row| { row.iter_mut() .zip(acc) - .map(|(&mut m_val, acc_val)| acc_val + m_val) + .map(|(&mut m_val, acc_val)| acc_val.saturating_add(m_val)) .collect() }); x.iter_mut().for_each(|row| { @@ -406,7 +423,7 @@ pub fn inplace_col_normalize(x: &mut [Vec]) { .zip(&col_sums) .filter(|(_, col_sum)| **col_sum != I32F32::from_num(0_f32)) .for_each(|(m_val, col_sum)| { - *m_val /= col_sum; + *m_val = m_val.saturating_div(*col_sum); }); }); } @@ -427,7 +444,7 @@ pub fn inplace_col_max_upscale_sparse(sparse_matrix: &mut [Vec<(u16, I32F32)>], if col_max[*j as usize] == I32F32::from_num(0.0_f32) { continue; } - *value /= col_max[*j as usize]; + *value = value.saturating_div(col_max[*j as usize]); } } } @@ -455,7 +472,7 @@ pub fn inplace_col_max_upscale(x: &mut [Vec]) { .zip(&col_maxes) .filter(|(_, col_max)| **col_max != I32F32::from_num(0)) .for_each(|(m_val, col_max)| { - *m_val /= col_max; + *m_val = m_val.saturating_div(*col_max); }); }); } @@ -604,7 +621,11 @@ pub fn row_hadamard(matrix: &[Vec], vector: &[I32F32]) -> Vec], vector: &[I32F32]) -> Vec { // Compute ranks: r_j = SUM(i) w_ij * s_i // Compute trust scores: t_j = SUM(i) w_ij * s_i // result_j = SUM(i) vector_i * matrix_ij - acc_val + vec_val * m_val + acc_val.saturating_add(vec_val.saturating_mul(*m_val)) }) .collect() }, @@ -674,7 +695,7 @@ pub fn matmul_64(matrix: &[Vec], vector: &[I64F64]) -> Vec { // Compute ranks: r_j = SUM(i) w_ij * s_i // Compute trust scores: t_j = SUM(i) w_ij * s_i // result_j = SUM(i) vector_i * matrix_ij - acc_val + vec_val * m_val + acc_val.saturating_add(vec_val.saturating_mul(*m_val)) }) .collect() }) @@ -699,7 +720,7 @@ pub fn matmul_transpose(matrix: &[Vec], vector: &[I32F32]) -> Vec = (0..use_stake.len()).collect(); - let minority: I32F32 = stake_sum - majority; + let minority: I32F32 = stake_sum.saturating_sub(majority); let mut use_score: Vec> = vec![vec![zero; use_stake.len()]; columns as usize]; let mut median: Vec = vec![zero; columns as usize]; let mut k: usize = 0; @@ -978,7 +1002,7 @@ pub fn weighted_median_col_sparse( for (c, val) in score[r].iter() { use_score[*c as usize][k] = *val; } - k += 1; + k.saturating_inc(); } for c in 0..columns as usize { median[c] = weighted_median( @@ -1009,7 +1033,7 @@ pub fn hadamard(mat1: &[Vec], mat2: &[Vec]) -> Vec> assert!(row1.len() == row2.len()); row1.iter() .zip(row2) - .map(|(elem1, elem2)| elem1 * elem2) + .map(|(elem1, elem2)| elem1.saturating_mul(*elem2)) .collect() }) .collect() @@ -1029,14 +1053,14 @@ pub fn hadamard_sparse( for i in 0..rows { let mut row1: Vec = vec![zero; columns as usize]; for (j, value) in mat1[i].iter() { - row1[*j as usize] += value; + row1[*j as usize] = row1[*j as usize].saturating_add(*value); } let mut row2: Vec = vec![zero; columns as usize]; for (j, value) in mat2[i].iter() { - row2[*j as usize] += value; + row2[*j as usize] = row2[*j as usize].saturating_add(*value); } for j in 0..columns as usize { - let prod: I32F32 = row1[j] * row2[j]; + let prod: I32F32 = row1[j].saturating_mul(row2[j]); if zero < prod { result[i].push((j as u16, prod)) } @@ -1056,14 +1080,18 @@ pub fn mat_ema(new: &[Vec], old: &[Vec], alpha: I32F32) -> Vec> = vec![vec![]; n]; for i in 0..new.len() { let mut row: Vec = vec![zero; n]; for (j, value) in new[i].iter() { - row[*j as usize] += alpha * value; + row[*j as usize] = row[*j as usize].saturating_add(alpha.saturating_mul(*value)); } for (j, value) in old[i].iter() { - row[*j as usize] += one_minus_alpha * value; + row[*j as usize] = + row[*j as usize].saturating_add(one_minus_alpha.saturating_mul(*value)); } for (j, value) in row.iter().enumerate() { if *value > zero { @@ -1114,7 +1143,11 @@ pub fn sparse_threshold(w: &[Vec<(u16, I32F32)>], threshold: I32F32) -> Vec(test: bool) -> Weight { weight = weight.saturating_add(T::DbWeight::get().reads(1)); // Compute the total issuance value - let total_issuance_value: u64 = stake_sum + total_balance_sum + locked_sum; + let total_issuance_value: u64 = stake_sum + .saturating_add(total_balance_sum) + .saturating_add(locked_sum); // Update the total issuance in storage TotalIssuance::::put(total_issuance_value); @@ -134,7 +137,7 @@ pub fn migrate_create_root_network() -> Weight { NetworksAdded::::insert(root_netuid, true); // Increment the number of total networks. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_inc()); // Set the maximum number to the number of senate members. MaxAllowedUids::::insert(root_netuid, 64); @@ -201,7 +204,7 @@ pub fn migrate_delete_subnet_3() -> Weight { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); @@ -285,7 +288,7 @@ pub fn migrate_delete_subnet_21() -> Weight { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 88730f7c3..dda00db54 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -2,6 +2,7 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; use sp_core::{Get, H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; +use sp_runtime::Saturating; use system::pallet_prelude::BlockNumberFor; const LOG_TARGET: &str = "runtime::subtensor::registration"; @@ -74,7 +75,7 @@ impl Pallet { // --- 4. Ensure we are not exceeding the max allowed registrations per interval. ensure!( Self::get_registrations_this_interval(netuid) - < Self::get_target_registrations_per_interval(netuid) * 3, + < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); @@ -143,9 +144,9 @@ impl Pallet { } // --- 14. Record the registration and increment block and interval counters. - BurnRegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisBlock::::mutate(netuid, |val| *val += 1); + BurnRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); Self::increase_rao_recycled(netuid, Self::get_burn_as_u64(netuid)); // --- 15. Deposit successful event. @@ -259,7 +260,7 @@ impl Pallet { // --- 5. Ensure we are not exceeding the max allowed registrations per interval. ensure!( Self::get_registrations_this_interval(netuid) - < Self::get_target_registrations_per_interval(netuid) * 3, + < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); @@ -277,7 +278,7 @@ impl Pallet { Error::::InvalidWorkBlock ); ensure!( - current_block_number - block_number < 3, + current_block_number.saturating_sub(block_number) < 3, Error::::InvalidWorkBlock ); @@ -338,9 +339,9 @@ impl Pallet { } // --- 12. Record the registration and increment block and interval counters. - POWRegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisInterval::::mutate(netuid, |val| *val += 1); - RegistrationsThisBlock::::mutate(netuid, |val| *val += 1); + POWRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); + RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); // --- 13. Deposit successful event. log::info!( @@ -376,7 +377,7 @@ impl Pallet { Error::::InvalidWorkBlock ); ensure!( - current_block_number - block_number < 3, + current_block_number.saturating_sub(block_number) < 3, Error::::InvalidWorkBlock ); @@ -440,7 +441,7 @@ impl Pallet { Self::get_neuron_block_at_registration(netuid, neuron_uid_i); #[allow(clippy::comparison_chain)] if min_score == pruning_score { - if current_block - block_at_registration < immunity_period { + if current_block.saturating_sub(block_at_registration) < immunity_period { //neuron is in immunity period if min_score_in_immunity_period > pruning_score { min_score_in_immunity_period = pruning_score; @@ -452,7 +453,7 @@ impl Pallet { } // Find min pruning score. else if min_score > pruning_score { - if current_block - block_at_registration < immunity_period { + if current_block.saturating_sub(block_at_registration) < immunity_period { //neuron is in immunity period if min_score_in_immunity_period > pruning_score { min_score_in_immunity_period = pruning_score; @@ -584,7 +585,7 @@ impl Pallet { let mut nonce: u64 = start_nonce; let mut work: H256 = Self::create_seal_hash(block_number, nonce, hotkey); while !Self::hash_meets_difficulty(&work, difficulty) { - nonce += 1; + nonce.saturating_inc(); work = Self::create_seal_hash(block_number, nonce, hotkey); } let vec_work: Vec = Self::hash_to_vec(work); @@ -618,8 +619,9 @@ impl Pallet { Error::::HotKeyAlreadyRegisteredInSubNet ); - weight - .saturating_accrue(T::DbWeight::get().reads((TotalNetworks::::get() + 1u16) as u64)); + weight.saturating_accrue( + T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1)) as u64), + ); let swap_cost = 1_000_000_000u64; ensure!( diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index a09957924..9e0327fb3 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -21,6 +21,7 @@ use frame_support::dispatch::Pays; use frame_support::storage::{IterableStorageDoubleMap, IterableStorageMap}; use frame_support::traits::Get; use frame_support::weights::Weight; +use sp_runtime::Saturating; use sp_std::vec; use substrate_fixed::{ transcendental::log2, @@ -156,9 +157,19 @@ impl Pallet { // Calculate the logarithmic residual of the issuance against half the total supply. let residual: I96F32 = log2( I96F32::from_num(1.0) - / (I96F32::from_num(1.0) - - total_issuance - / (I96F32::from_num(2.0) * I96F32::from_num(10_500_000_000_000_000.0))), + .checked_div( + I96F32::from_num(1.0) + .checked_sub( + total_issuance + .checked_div( + I96F32::from_num(2.0) + .saturating_mul(I96F32::from_num(10_500_000_000_000_000.0)), + ) + .ok_or("Logarithm calculation failed")?, + ) + .ok_or("Logarithm calculation failed")?, + ) + .ok_or("Logarithm calculation failed")?, ) .map_err(|_| "Logarithm calculation failed")?; // Floor the residual to smooth out the emission rate. @@ -169,12 +180,12 @@ impl Pallet { // Multiply 2.0 by itself floored_residual times to calculate the power of 2. let mut multiplier: I96F32 = I96F32::from_num(1.0); for _ in 0..floored_residual_int { - multiplier *= I96F32::from_num(2.0); + multiplier = multiplier.saturating_mul(I96F32::from_num(2.0)); } - let block_emission_percentage: I96F32 = I96F32::from_num(1.0) / multiplier; + let block_emission_percentage: I96F32 = I96F32::from_num(1.0).saturating_div(multiplier); // Calculate the actual emission based on the emission rate - let block_emission: I96F32 = - block_emission_percentage * I96F32::from_num(DefaultBlockEmission::::get()); + let block_emission: I96F32 = block_emission_percentage + .saturating_mul(I96F32::from_num(DefaultBlockEmission::::get())); // Convert to u64 let block_emission_u64: u64 = block_emission.to_num::(); if BlockEmission::::get() != block_emission_u64 { @@ -384,10 +395,10 @@ impl Pallet { let mut trust = vec![I64F64::from_num(0); total_networks as usize]; let mut total_stake: I64F64 = I64F64::from_num(0); for (weights, hotkey_stake) in weights.iter().zip(stake_i64) { - total_stake += hotkey_stake; + total_stake = total_stake.saturating_add(hotkey_stake); for (weight, trust_score) in weights.iter().zip(&mut trust) { if *weight > 0 { - *trust_score += hotkey_stake; + *trust_score = trust_score.saturating_add(hotkey_stake); } } } @@ -411,13 +422,15 @@ impl Pallet { let one = I64F64::from_num(1); let mut consensus = vec![I64F64::from_num(0); total_networks as usize]; for (trust_score, consensus_i) in trust.iter_mut().zip(&mut consensus) { - let shifted_trust = *trust_score - I64F64::from_num(Self::get_float_kappa(0)); // Range( -kappa, 1 - kappa ) - let temperatured_trust = shifted_trust * I64F64::from_num(Self::get_rho(0)); // Range( -rho * kappa, rho ( 1 - kappa ) ) + let shifted_trust = + trust_score.saturating_sub(I64F64::from_num(Self::get_float_kappa(0))); // Range( -kappa, 1 - kappa ) + let temperatured_trust = + shifted_trust.saturating_mul(I64F64::from_num(Self::get_rho(0))); // Range( -rho * kappa, rho ( 1 - kappa ) ) let exponentiated_trust: I64F64 = - substrate_fixed::transcendental::exp(-temperatured_trust) + substrate_fixed::transcendental::exp(temperatured_trust.saturating_neg()) .expect("temperatured_trust is on range( -rho * kappa, rho ( 1 - kappa ) )"); - *consensus_i = one / (one + exponentiated_trust); + *consensus_i = one.saturating_div(one.saturating_add(exponentiated_trust)); } log::debug!("C:\n{:?}\n", &consensus); @@ -425,7 +438,7 @@ impl Pallet { for ((emission, consensus_i), rank) in weighted_emission.iter_mut().zip(&consensus).zip(&ranks) { - *emission = *consensus_i * (*rank); + *emission = consensus_i.saturating_mul(*rank); } inplace_normalize_64(&mut weighted_emission); log::debug!("Ei64:\n{:?}\n", &weighted_emission); @@ -433,7 +446,7 @@ impl Pallet { // -- 11. Converts the normalized 64-bit fixed point rank values to u64 for the final emission calculation. let emission_as_tao: Vec = weighted_emission .iter() - .map(|v: &I64F64| *v * block_emission) + .map(|v: &I64F64| v.saturating_mul(block_emission)) .collect(); // --- 12. Converts the normalized 64-bit fixed point rank values to u64 for the final emission calculation. @@ -486,7 +499,7 @@ impl Pallet { // --- 3. Ensure that the number of registrations in this interval doesn't exceed thrice the target limit. ensure!( Self::get_registrations_this_interval(root_netuid) - < Self::get_target_registrations_per_interval(root_netuid) * 3, + < Self::get_target_registrations_per_interval(root_netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); @@ -584,8 +597,8 @@ impl Pallet { } // --- 14. Update the registration counters for both the block and interval. - RegistrationsThisInterval::::mutate(root_netuid, |val| *val += 1); - RegistrationsThisBlock::::mutate(root_netuid, |val| *val += 1); + RegistrationsThisInterval::::mutate(root_netuid, |val| val.saturating_inc()); + RegistrationsThisBlock::::mutate(root_netuid, |val| val.saturating_inc()); // --- 15. Log and announce the successful registration. log::info!( @@ -812,7 +825,7 @@ impl Pallet { // We subtract one because we don't want root subnet to count towards total let mut next_available_netuid = 0; loop { - next_available_netuid += 1; + next_available_netuid.saturating_inc(); if !Self::if_subnet_exist(next_available_netuid) { log::debug!("got subnet id: {:?}", next_available_netuid); break next_available_netuid; @@ -911,7 +924,7 @@ impl Pallet { NetworkModality::::insert(netuid, 0); // --- 5. Increase total network count. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_inc()); // --- 6. Set all default values **explicitly**. Self::set_network_registration_allowed(netuid, true); @@ -1003,7 +1016,7 @@ impl Pallet { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_dec()); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); @@ -1067,6 +1080,7 @@ impl Pallet { SubnetOwner::::remove(netuid); } + #[allow(clippy::arithmetic_side_effects)] /// This function calculates the lock cost for a network based on the last lock amount, minimum lock cost, last lock block, and current block. /// The lock cost is calculated using the formula: /// lock_cost = (last_lock * mult) - (last_lock / lock_reduction_interval) * (current_block - last_lock_block) diff --git a/pallets/subtensor/src/serving.rs b/pallets/subtensor/src/serving.rs index 116d95982..eb7fa4369 100644 --- a/pallets/subtensor/src/serving.rs +++ b/pallets/subtensor/src/serving.rs @@ -222,7 +222,7 @@ impl Pallet { ) -> bool { let rate_limit: u64 = Self::get_serving_rate_limit(netuid); let last_serve = prev_axon_info.block; - rate_limit == 0 || last_serve == 0 || current_block - last_serve >= rate_limit + rate_limit == 0 || last_serve == 0 || current_block.saturating_sub(last_serve) >= rate_limit } pub fn prometheus_passes_rate_limit( @@ -232,7 +232,7 @@ impl Pallet { ) -> bool { let rate_limit: u64 = Self::get_serving_rate_limit(netuid); let last_serve = prev_prometheus_info.block; - rate_limit == 0 || last_serve == 0 || current_block - last_serve >= rate_limit + rate_limit == 0 || last_serve == 0 || current_block.saturating_sub(last_serve) >= rate_limit } pub fn has_axon_info(netuid: u16, hotkey: &T::AccountId) -> bool { diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 08b65b8a7..7c3328396 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -346,7 +346,7 @@ impl Pallet { Self::set_stakes_this_interval_for_coldkey_hotkey( &coldkey, &hotkey, - stakes_this_interval + 1, + stakes_this_interval.saturating_add(1), block, ); log::info!( @@ -452,7 +452,7 @@ impl Pallet { Self::set_stakes_this_interval_for_coldkey_hotkey( &coldkey, &hotkey, - unstakes_this_interval + 1, + unstakes_this_interval.saturating_add(1), block, ); log::info!( @@ -530,7 +530,7 @@ impl Pallet { TotalHotkeyColdkeyStakesThisInterval::::get(coldkey, hotkey); // Calculate the block number after which the stakes for the hotkey should be reset. - let block_to_reset_after = block_last_staked_at + stake_interval; + let block_to_reset_after = block_last_staked_at.saturating_add(stake_interval); // If the current block number is beyond the reset point, // it indicates the end of the staking interval for the hotkey. diff --git a/pallets/subtensor/src/subnet_info.rs b/pallets/subtensor/src/subnet_info.rs index cf6b66aea..ef92b2da6 100644 --- a/pallets/subtensor/src/subnet_info.rs +++ b/pallets/subtensor/src/subnet_info.rs @@ -117,7 +117,7 @@ impl Pallet { } let mut subnets_info = Vec::>>::new(); - for netuid_ in 0..(max_netuid + 1) { + for netuid_ in 0..=max_netuid { if subnet_netuids.contains(&netuid_) { subnets_info.push(Self::get_subnet_info(netuid_)); } diff --git a/pallets/subtensor/src/uids.rs b/pallets/subtensor/src/uids.rs index 4ae2c24de..fff358f1c 100644 --- a/pallets/subtensor/src/uids.rs +++ b/pallets/subtensor/src/uids.rs @@ -59,7 +59,7 @@ impl Pallet { ); // 2. Get and increase the uid count. - SubnetworkN::::insert(netuid, next_uid + 1); + SubnetworkN::::insert(netuid, next_uid.saturating_add(1)); // 3. Expand Yuma Consensus with new position. Rank::::mutate(netuid, |v| v.push(0)); @@ -126,11 +126,7 @@ impl Pallet { /// Return the total number of subnetworks available on the chain. /// pub fn get_number_of_subnets() -> u16 { - let mut number_of_subnets: u16 = 0; - for (_, _) in as IterableStorageMap>::iter() { - number_of_subnets += 1; - } - number_of_subnets + as IterableStorageMap>::iter().count() as u16 } /// Return a list of all networks a hotkey is registered on. diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 54b7818c9..546875b27 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -291,7 +291,7 @@ impl Pallet { return false; } - current_block - prev_tx_block <= rate_limit + current_block.saturating_sub(prev_tx_block) <= rate_limit } pub fn exceeds_tx_delegate_take_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { let rate_limit: u64 = Self::get_tx_delegate_take_rate_limit(); @@ -299,7 +299,7 @@ impl Pallet { return false; } - current_block - prev_tx_block <= rate_limit + current_block.saturating_sub(prev_tx_block) <= rate_limit } // ======================== diff --git a/pallets/subtensor/src/weights.rs b/pallets/subtensor/src/weights.rs index 548bc30af..1866f8e62 100644 --- a/pallets/subtensor/src/weights.rs +++ b/pallets/subtensor/src/weights.rs @@ -325,7 +325,8 @@ impl Pallet { if last_set_weights == 0 { return true; } // (Storage default) Never set weights. - return (current_block - last_set_weights) >= Self::get_weights_set_rate_limit(netuid); + return current_block.saturating_sub(last_set_weights) + >= Self::get_weights_set_rate_limit(netuid); } // --- 3. Non registered peers cant pass. false @@ -398,6 +399,7 @@ impl Pallet { false } + #[allow(clippy::arithmetic_side_effects)] /// Returns normalized the passed positive integer weights so that they sum to u16 max value. pub fn normalize_weights(mut weights: Vec) -> Vec { let sum: u64 = weights.iter().map(|x| *x as u64).sum(); @@ -405,7 +407,9 @@ impl Pallet { return weights; } weights.iter_mut().for_each(|x| { - *x = (*x as u64 * u16::MAX as u64 / sum) as u16; + *x = (*x as u64) + .saturating_mul(u16::MAX as u64) + .saturating_div(sum) as u16; }); weights } @@ -448,6 +452,7 @@ impl Pallet { uids.len() <= subnetwork_n as usize } + #[allow(clippy::arithmetic_side_effects)] pub fn can_commit(netuid: u16, who: &T::AccountId) -> bool { if let Some((_hash, commit_block)) = WeightCommits::::get(netuid, who) { let interval: u64 = Self::get_commit_reveal_weights_interval(netuid); @@ -456,11 +461,12 @@ impl Pallet { } let current_block: u64 = Self::get_current_block_as_u64(); - let interval_start: u64 = current_block - (current_block % interval); - let last_commit_interval_start: u64 = commit_block - (commit_block % interval); + let interval_start: u64 = current_block.saturating_sub(current_block % interval); + let last_commit_interval_start: u64 = + commit_block.saturating_sub(commit_block % interval); // Allow commit if we're within the interval bounds - if current_block <= interval_start + interval + if current_block <= interval_start.saturating_add(interval) && interval_start > last_commit_interval_start { return true; @@ -472,19 +478,20 @@ impl Pallet { } } + #[allow(clippy::arithmetic_side_effects)] pub fn is_reveal_block_range(netuid: u16, commit_block: u64) -> bool { let interval: u64 = Self::get_commit_reveal_weights_interval(netuid); if interval == 0 { return true; //prevent division by 0 } - let commit_interval_start: u64 = commit_block - (commit_block % interval); // Find the start of the interval in which the commit occurred - let reveal_interval_start: u64 = commit_interval_start + interval; // Start of the next interval after the commit interval + let commit_interval_start: u64 = commit_block.saturating_sub(commit_block % interval); // Find the start of the interval in which the commit occurred + let reveal_interval_start: u64 = commit_interval_start.saturating_add(interval); // Start of the next interval after the commit interval let current_block: u64 = Self::get_current_block_as_u64(); // Allow reveal if the current block is within the interval following the commit's interval if current_block >= reveal_interval_start - && current_block < reveal_interval_start + interval + && current_block < reveal_interval_start.saturating_add(interval) { return true; } diff --git a/pallets/subtensor/tests/block_step.rs b/pallets/subtensor/tests/block_step.rs index 5df173906..ab99363ed 100644 --- a/pallets/subtensor/tests/block_step.rs +++ b/pallets/subtensor/tests/block_step.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + mod mock; use frame_support::assert_ok; use frame_system::Config; @@ -5,7 +7,6 @@ use mock::*; use sp_core::U256; #[test] -#[allow(clippy::unwrap_used)] fn test_loaded_emission() { new_test_ext(1).execute_with(|| { let n: u16 = 100; diff --git a/pallets/subtensor/tests/difficulty.rs b/pallets/subtensor/tests/difficulty.rs index 24552261d..05238bc43 100644 --- a/pallets/subtensor/tests/difficulty.rs +++ b/pallets/subtensor/tests/difficulty.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use crate::mock::*; mod mock; use sp_core::U256; diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index fe247fd32..635b4fc0b 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -1,3 +1,9 @@ +#![allow( + clippy::arithmetic_side_effects, + clippy::indexing_slicing, + clippy::unwrap_used +)] + use crate::mock::*; use frame_support::assert_ok; use frame_system::Config; diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 2f634d7c0..c921124f4 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + mod mock; use frame_support::assert_ok; use frame_system::Config; diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 9995acf84..d772a8d47 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -1,7 +1,9 @@ -use frame_support::derive_impl; -use frame_support::dispatch::DispatchResultWithPostInfo; +#![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] + use frame_support::{ - assert_ok, parameter_types, + assert_ok, derive_impl, + dispatch::DispatchResultWithPostInfo, + parameter_types, traits::{Everything, Hooks}, weights, }; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 5ee941f26..0da10bc48 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use frame_support::traits::Currency; use crate::mock::*; diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 7958c9c81..7c6622670 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -1,3 +1,5 @@ +#![allow(clippy::indexing_slicing, clippy::unwrap_used)] + use crate::mock::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; diff --git a/pallets/subtensor/tests/senate.rs b/pallets/subtensor/tests/senate.rs index a21fbce01..ac020ae04 100644 --- a/pallets/subtensor/tests/senate.rs +++ b/pallets/subtensor/tests/senate.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + mod mock; use mock::*; diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index ffe9de27a..766b3a495 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::Config; mod mock; diff --git a/pallets/subtensor/tests/uids.rs b/pallets/subtensor/tests/uids.rs index b8a969943..82adc6b8a 100644 --- a/pallets/subtensor/tests/uids.rs +++ b/pallets/subtensor/tests/uids.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use crate::mock::*; use frame_support::assert_ok; use frame_system::Config; diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 5cd3bf7c2..2344bd425 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -1,3 +1,5 @@ +#![allow(clippy::indexing_slicing)] + mod mock; use frame_support::{ assert_err, assert_ok, diff --git a/runtime/src/check_nonce.rs b/runtime/src/check_nonce.rs index d9948f9b6..b257d6a49 100644 --- a/runtime/src/check_nonce.rs +++ b/runtime/src/check_nonce.rs @@ -8,6 +8,7 @@ use sp_runtime::{ InvalidTransaction, TransactionLongevity, TransactionValidity, TransactionValidityError, ValidTransaction, }, + Saturating, }; use sp_std::vec; @@ -82,7 +83,7 @@ where } .into()); } - account.nonce += T::Nonce::one(); + account.nonce.saturating_inc(); frame_system::Account::::insert(who, account); Ok(()) } @@ -111,7 +112,7 @@ where let provides = vec![Encode::encode(&(who, self.0))]; let requires = if account.nonce < self.0 { - vec![Encode::encode(&(who, self.0 - One::one()))] + vec![Encode::encode(&(who, self.0.saturating_sub(One::one())))] } else { vec![] }; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2364008fd..8189f6032 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,6 +1,8 @@ #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +// Some arithmetic operations can't use the saturating equivalent, such as the PerThing types +#![allow(clippy::arithmetic_side_effects)] // Make the WASM binary available. #[cfg(feature = "std")] @@ -96,7 +98,9 @@ pub type Nonce = u32; pub const fn deposit(items: u32, bytes: u32) -> Balance { pub const ITEMS_FEE: Balance = 2_000 * 10_000; pub const BYTES_FEE: Balance = 100 * 10_000; - items as Balance * ITEMS_FEE + bytes as Balance * BYTES_FEE + (items as Balance) + .saturating_mul(ITEMS_FEE) + .saturating_add((bytes as Balance).saturating_mul(BYTES_FEE)) } // Opaque types. These are used by the CLI to instantiate machinery that don't need to know @@ -662,7 +666,11 @@ impl PrivilegeCmp for OriginPrivilegeCmp { r_yes_votes, r_count, )), // Equivalent to (l_yes_votes / l_count).cmp(&(r_yes_votes / r_count)) - ) => Some((l_yes_votes * r_count).cmp(&(r_yes_votes * l_count))), + ) => Some( + l_yes_votes + .saturating_mul(*r_count) + .cmp(&r_yes_votes.saturating_mul(*l_count)), + ), // For every other origin we don't care, as they are not used for `ScheduleOrigin`. _ => None, } @@ -1461,6 +1469,7 @@ impl_runtime_apis! { #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { + #[allow(clippy::unwrap_used)] fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to // have a backtrace here. If any of the pre/post migration checks fail, we shall stop diff --git a/runtime/tests/metadata.rs b/runtime/tests/metadata.rs index 692676d79..975c96227 100644 --- a/runtime/tests/metadata.rs +++ b/runtime/tests/metadata.rs @@ -1,3 +1,5 @@ +#![allow(clippy::indexing_slicing)] + use frame_metadata::RuntimeMetadata; use node_subtensor_runtime::Runtime; use scale_info::TypeDef; diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs index f04fda9f2..796dfc471 100644 --- a/runtime/tests/pallet_proxy.rs +++ b/runtime/tests/pallet_proxy.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unwrap_used)] + use codec::Encode; use frame_support::{assert_ok, traits::InstanceFilter, BoundedVec}; use node_subtensor_runtime::{ From 2c5b1ffbeb1331c03162930add56cc78d40962eb Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 19 Jun 2024 21:37:43 +0900 Subject: [PATCH 002/185] clippy fixes --- pallets/subtensor/src/math.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/math.rs index f7784ea1c..40ee7ebe8 100644 --- a/pallets/subtensor/src/math.rs +++ b/pallets/subtensor/src/math.rs @@ -252,7 +252,7 @@ pub fn inplace_normalize(x: &mut [I32F32]) { if x_sum == I32F32::from_num(0.0_f32) { return; } - x.into_iter() + x.iter_mut() .for_each(|value| *value = value.saturating_div(x_sum)); } @@ -262,7 +262,7 @@ pub fn inplace_normalize_using_sum(x: &mut [I32F32], x_sum: I32F32) { if x_sum == I32F32::from_num(0.0_f32) { return; } - x.into_iter() + x.iter_mut() .for_each(|value| *value = value.saturating_div(x_sum)); } @@ -273,7 +273,7 @@ pub fn inplace_normalize_64(x: &mut [I64F64]) { if x_sum == I64F64::from_num(0) { return; } - x.into_iter() + x.iter_mut() .for_each(|value| *value = value.saturating_div(x_sum)); } @@ -311,7 +311,7 @@ pub fn inplace_row_normalize(x: &mut [Vec]) { for row in x { let row_sum: I32F32 = row.iter().sum(); if row_sum > I32F32::from_num(0.0_f32) { - row.into_iter() + row.iter_mut() .for_each(|x_ij: &mut I32F32| *x_ij = x_ij.saturating_div(row_sum)); } } @@ -622,7 +622,7 @@ pub fn row_hadamard(matrix: &[Vec], vector: &[I32F32]) -> Vec Date: Wed, 19 Jun 2024 11:49:25 -0400 Subject: [PATCH 003/185] add requirement for `devnet-companion` label for `devnet-ready` PRs --- .github/workflows/devnet-ready-labels.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/devnet-ready-labels.yml diff --git a/.github/workflows/devnet-ready-labels.yml b/.github/workflows/devnet-ready-labels.yml new file mode 100644 index 000000000..25d9bc479 --- /dev/null +++ b/.github/workflows/devnet-ready-labels.yml @@ -0,0 +1,17 @@ +name: Companion Labels (devnet) +on: + pull_request: + types: [opened, labeled, unlabeled, synchronize] + branches: [devnet-ready] +jobs: + check-labels: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: mheap/github-action-required-labels@v5 + with: + mode: minimum + count: 1 + labels: devnet-companion From 25ceb3b497d53e63010add603ea4e7e02b612fed Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 19 Jun 2024 11:50:58 -0400 Subject: [PATCH 004/185] add testnet-ready labels check --- .github/workflows/devnet-ready-labels.yml | 2 +- .github/workflows/testnet-ready-labels.yml | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/testnet-ready-labels.yml diff --git a/.github/workflows/devnet-ready-labels.yml b/.github/workflows/devnet-ready-labels.yml index 25d9bc479..ab53327e7 100644 --- a/.github/workflows/devnet-ready-labels.yml +++ b/.github/workflows/devnet-ready-labels.yml @@ -1,4 +1,4 @@ -name: Companion Labels (devnet) +name: devnet-companion Label Check on: pull_request: types: [opened, labeled, unlabeled, synchronize] diff --git a/.github/workflows/testnet-ready-labels.yml b/.github/workflows/testnet-ready-labels.yml new file mode 100644 index 000000000..8570d2011 --- /dev/null +++ b/.github/workflows/testnet-ready-labels.yml @@ -0,0 +1,17 @@ +name: testnet-companion Label Check +on: + pull_request: + types: [opened, labeled, unlabeled, synchronize] + branches: [testnet-ready] +jobs: + check-labels: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: mheap/github-action-required-labels@v5 + with: + mode: minimum + count: 1 + labels: testnet-companion From 82cad04be874797143081bb232e25eec823563c2 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 19 Jun 2024 11:52:15 -0400 Subject: [PATCH 005/185] update CONTRIBUTING.md with additional info --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3616041b..084f3e960 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,9 +15,9 @@ add appropriate labels to your PR as shown below. Three positive reviews are required. 4. Once the required passing reviews have been obtained, you are ready to request that your PR be included in the next `devnet` deploy. To do this, you should open a companion PR merging - your branch into the `devnet-ready` branch. You must include a link to the parent PR in the - description and preface your PR title with "(Devnet Ready)" or the PR will be - closed/ignored. + a copy of your branch into the `devnet-ready` branch. You must include a link to the parent + PR in the description and preface your PR title with "(Devnet Ready)" or the PR will be + closed/ignored. Your companion PR should have the `devnet-companion` label. 5. A core team administrator will review your "(Devnet Ready)" PR, verifying that it logically matches the changes introduced in the parent PR (there will sometimes be minor differences due to merge conflicts) and will either request changes or approve the PR and merge it. Once From 509af18380478aacb8bf3be5ed9fcde54ca1757a Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 19 Jun 2024 11:55:14 -0400 Subject: [PATCH 006/185] update docs --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 084f3e960..132d360b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,11 +86,13 @@ | `runtime` | PR contains substantive changes to runtime / pallet code | none | | `breaking-change` | PR requires synchronized changes with bittensor | Triggers an automatic bot message so the relevant teams are made aware of the change well in advance | | `migration` | PR contains one or more migrations | none | +| `devnet-companion` | Designates a devnet companion PR | Presence of `devnet-companion` label is checked | | `devnet-ready` | PR's branch has been merged into the `devnet-ready` branch and will be included in the next `devnet` deploy | none | | `on-devnet` | PR has been deployed to `devnet` | Removes `devnet-ready` | | `devnet-pass` | PR has passed manual testing on `devnet` | `devnet-pass` or `devnet-skip` required | | `devnet-skip` | Allows a critical hotfix PR to skip required testing on `devnet` | `devnet-pass` or `devnet-skip` required | | `devnet-fail` | PR has failed manual testing on `devnet` and requires modification | none | +| `testnet-companion` | Designates a testnet companion PR | Presence of `testnet-companion` label is checked | | `on-testnet` | PR has been deployed to `testnet` | none | | `testnet-pass` | PR has passed manual testing on `testnet` | `testnet-pass` or `testnet-skip` required | | `testnet-skip` | Allows a critical hotfix PR to skip required manual testing and SOP on `testnet` | `testnet-pass` or `testnet-skip` required | From 9b221057d92d24fd4b7aafadbb88b7048d450753 Mon Sep 17 00:00:00 2001 From: Liam Date: Sun, 23 Jun 2024 14:37:20 +0300 Subject: [PATCH 007/185] override default pages to 60k --- node/src/command.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/node/src/command.rs b/node/src/command.rs index 23674ad17..2423d1456 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -17,7 +17,7 @@ use sp_runtime::traits::HashingFor; use node_subtensor_runtime::Block; use sc_cli::SubstrateCli; -use sc_service::PartialComponents; +use sc_service::{Configuration, PartialComponents}; impl SubstrateCli for Cli { fn impl_name() -> String { @@ -209,8 +209,56 @@ pub fn run() -> sc_cli::Result<()> { None => { let runner = cli.create_runner(&cli.run)?; runner.run_node_until_exit(|config| async move { + let config = override_default_heap_pages(config, 60_000); service::new_full(config).map_err(sc_cli::Error::Service) }) } } } + +/// Override default heap pages +fn override_default_heap_pages(config: Configuration, pages: u64) -> Configuration { + Configuration { + default_heap_pages: Some(pages), + impl_name: config.impl_name, + impl_version: config.impl_version, + role: config.role, + tokio_handle: config.tokio_handle, + transaction_pool: config.transaction_pool, + network: config.network, + keystore: config.keystore, + database: config.database, + trie_cache_maximum_size: config.trie_cache_maximum_size, + state_pruning: config.state_pruning, + blocks_pruning: config.blocks_pruning, + chain_spec: config.chain_spec, + wasm_method: config.wasm_method, + wasm_runtime_overrides: config.wasm_runtime_overrides, + rpc_addr: config.rpc_addr, + rpc_max_connections: config.rpc_max_connections, + rpc_cors: config.rpc_cors, + rpc_methods: config.rpc_methods, + rpc_max_request_size: config.rpc_max_request_size, + rpc_max_response_size: config.rpc_max_response_size, + rpc_id_provider: config.rpc_id_provider, + rpc_max_subs_per_conn: config.rpc_max_subs_per_conn, + rpc_port: config.rpc_port, + rpc_message_buffer_capacity: config.rpc_message_buffer_capacity, + rpc_batch_config: config.rpc_batch_config, + rpc_rate_limit: config.rpc_rate_limit, + prometheus_config: config.prometheus_config, + telemetry_endpoints: config.telemetry_endpoints, + offchain_worker: config.offchain_worker, + force_authoring: config.force_authoring, + disable_grandpa: config.disable_grandpa, + dev_key_seed: config.dev_key_seed, + tracing_targets: config.tracing_targets, + tracing_receiver: config.tracing_receiver, + max_runtime_instances: config.max_runtime_instances, + announce_block: config.announce_block, + data_path: config.data_path, + base_path: config.base_path, + informant_output_format: config.informant_output_format, + runtime_cache_size: config.runtime_cache_size, + } +} From 43a23f46a9a7d480836398af9a6fdcff628424cd Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 24 Jun 2024 13:26:54 +0300 Subject: [PATCH 008/185] update doc --- docs/running-subtensor-locally.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/running-subtensor-locally.md b/docs/running-subtensor-locally.md index 4d827d858..089f4d30a 100644 --- a/docs/running-subtensor-locally.md +++ b/docs/running-subtensor-locally.md @@ -174,7 +174,7 @@ You can now run the public subtensor node either as a lite node or as an archive To run a lite node connected to the mainchain, execute the below command (note the `--sync=warp` flag which runs the subtensor node in lite mode): ```bash title="With --sync=warp setting, for lite node" -./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=warp --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Archive node on mainchain @@ -182,7 +182,7 @@ To run a lite node connected to the mainchain, execute the below command (note t To run an archive node connected to the mainchain, execute the below command (note the `--sync=full` which syncs the node to the full chain and `--pruning archive` flags, which disables the node's automatic pruning of older historical data): ```bash title="With --sync=full and --pruning archive setting, for archive node" -./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=full --pruning archive --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Lite node on testchain @@ -190,7 +190,7 @@ To run an archive node connected to the mainchain, execute the below command (no To run a lite node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=warp setting, for lite node." -./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=warp --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Archive node on testchain @@ -198,8 +198,9 @@ To run a lite node connected to the testchain, execute the below command: To run an archive node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=full and --pruning archive setting, for archive node" -./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=full --pruning archive --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ## Running on cloud + We have not tested these installation scripts on any cloud service. In addition, if you are using Runpod cloud service, then note that this service is already [containerized](https://docs.runpod.io/pods/overview). Hence, the only option available to you is to compile from the source, as described in the above [Method 2: Using Source Code](#method-2-using-source-code) section. Note that these scripts have not been tested on Runpod. From c5865b443098cdbadbc7ddf13f5b5f0c4b519868 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 24 Jun 2024 13:43:52 +0300 Subject: [PATCH 009/185] update docs to use production profile remove --execution and --wasm-execution --- Dockerfile | 4 +- docs/running-subtensor-locally.md | 11 +- docs/rust-setup.md | 6 +- pallets/admin-utils/scripts/benchmark.sh | 20 ++- pallets/commitments/scripts/benchmark.sh | 16 +- pallets/registry/scripts/benchmark.sh | 14 +- scripts/benchmark.sh | 53 ++++--- scripts/build.sh | 3 +- scripts/run/subtensor.sh | 178 +++++++++++------------ 9 files changed, 148 insertions(+), 157 deletions(-) diff --git a/Dockerfile b/Dockerfile index b20dc2d3b..26a925f70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ RUN /subtensor/scripts/init.sh # Cargo build WORKDIR /subtensor -RUN cargo build --release --features runtime-benchmarks --locked +RUN cargo build --profile production --features runtime-benchmarks --locked EXPOSE 30333 9933 9944 @@ -61,4 +61,4 @@ FROM $BASE_IMAGE AS subtensor COPY --from=builder /subtensor/snapshot.json / COPY --from=builder /subtensor/raw_spec.json / COPY --from=builder /subtensor/raw_testspec.json / -COPY --from=builder /subtensor/target/release/node-subtensor /usr/local/bin +COPY --from=builder /subtensor/target/production/node-subtensor /usr/local/bin diff --git a/docs/running-subtensor-locally.md b/docs/running-subtensor-locally.md index 4d827d858..505fe2fb5 100644 --- a/docs/running-subtensor-locally.md +++ b/docs/running-subtensor-locally.md @@ -162,7 +162,7 @@ rm -rf /tmp/blockchain Install subtensor by compiling with `cargo`: ```bash -cargo build --release --features=runtime-benchmarks +cargo build --profile production --features=runtime-benchmarks ``` ## Run the subtensor node @@ -174,7 +174,7 @@ You can now run the public subtensor node either as a lite node or as an archive To run a lite node connected to the mainchain, execute the below command (note the `--sync=warp` flag which runs the subtensor node in lite mode): ```bash title="With --sync=warp setting, for lite node" -./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/production/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=warp --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Archive node on mainchain @@ -182,7 +182,7 @@ To run a lite node connected to the mainchain, execute the below command (note t To run an archive node connected to the mainchain, execute the below command (note the `--sync=full` which syncs the node to the full chain and `--pruning archive` flags, which disables the node's automatic pruning of older historical data): ```bash title="With --sync=full and --pruning archive setting, for archive node" -./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/production/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=full --pruning archive --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Lite node on testchain @@ -190,7 +190,7 @@ To run an archive node connected to the mainchain, execute the below command (no To run a lite node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=warp setting, for lite node." -./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/production/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=warp --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Archive node on testchain @@ -198,8 +198,9 @@ To run a lite node connected to the testchain, execute the below command: To run an archive node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=full and --pruning archive setting, for archive node" -./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/production/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=full --pruning archive --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ## Running on cloud + We have not tested these installation scripts on any cloud service. In addition, if you are using Runpod cloud service, then note that this service is already [containerized](https://docs.runpod.io/pods/overview). Hence, the only option available to you is to compile from the source, as described in the above [Method 2: Using Source Code](#method-2-using-source-code) section. Note that these scripts have not been tested on Runpod. diff --git a/docs/rust-setup.md b/docs/rust-setup.md index 2755966e3..1527d5b8b 100644 --- a/docs/rust-setup.md +++ b/docs/rust-setup.md @@ -1,8 +1,7 @@ --- title: Installation --- - -This guide is for reference only, please check the latest information on getting starting with Substrate +This guide is for reference only, please check the latest information on getting starting with Substrate [here](https://docs.substrate.io/main-docs/install/). This page will guide you through the **2 steps** needed to prepare a computer for **Substrate** development. @@ -207,7 +206,7 @@ Use the `WASM_BUILD_TOOLCHAIN` environment variable to specify the Rust nightly project should use for Wasm compilation: ```bash -WASM_BUILD_TOOLCHAIN=nightly- cargo build --release +WASM_BUILD_TOOLCHAIN=nightly- cargo build --profile production ``` > Note that this only builds _the runtime_ with the specified nightly. The rest of project will be @@ -223,3 +222,4 @@ rustup uninstall nightly rustup install nightly- rustup target add wasm32-unknown-unknown --toolchain nightly- ``` + diff --git a/pallets/admin-utils/scripts/benchmark.sh b/pallets/admin-utils/scripts/benchmark.sh index b299e8dcb..82d417ffd 100755 --- a/pallets/admin-utils/scripts/benchmark.sh +++ b/pallets/admin-utils/scripts/benchmark.sh @@ -1,11 +1,9 @@ -cargo build --release --features runtime-benchmarks -./target/release/node-subtensor benchmark pallet \ - --chain=local \ - --execution=wasm \ - --wasm-execution=compiled \ - --pallet=pallet_admin_utils \ - --extrinsic="*" \ - --steps 50 \ - --repeat 20 \ - --output=pallets/admin-utils/src/weights.rs \ - --template=./.maintain/frame-weight-template.hbs \ No newline at end of file +cargo build --profile production --features runtime-benchmarks +./target/production/node-subtensor benchmark pallet \ + --chain=local \ + --pallet=pallet_admin_utils \ + --extrinsic="*" \ + --steps 50 \ + --repeat 20 \ + --output=pallets/admin-utils/src/weights.rs \ + --template=./.maintain/frame-weight-template.hbs diff --git a/pallets/commitments/scripts/benchmark.sh b/pallets/commitments/scripts/benchmark.sh index bf189bd8f..8a87c5bdd 100755 --- a/pallets/commitments/scripts/benchmark.sh +++ b/pallets/commitments/scripts/benchmark.sh @@ -1,9 +1,7 @@ -cargo build --release --features runtime-benchmarks -./target/release/node-subtensor benchmark pallet \ - --chain=local \ - --execution=wasm \ - --wasm-execution=compiled \ - --pallet=pallet_commitments \ - --extrinsic="*" \ - --output=pallets/commitments/src/weights.rs \ - --template=./.maintain/frame-weight-template.hbs \ No newline at end of file +cargo build --profile production --features runtime-benchmarks +./target/production/node-subtensor benchmark pallet \ + --chain=local \ + --pallet=pallet_commitments \ + --extrinsic="*" \ + --output=pallets/commitments/src/weights.rs \ + --template=./.maintain/frame-weight-template.hbs diff --git a/pallets/registry/scripts/benchmark.sh b/pallets/registry/scripts/benchmark.sh index 2bb8fabc8..ed1b28ead 100755 --- a/pallets/registry/scripts/benchmark.sh +++ b/pallets/registry/scripts/benchmark.sh @@ -1,9 +1,7 @@ cargo build --release --features runtime-benchmarks -./target/release/node-subtensor benchmark pallet \ - --chain=local \ - --execution=wasm \ - --wasm-execution=compiled \ - --pallet=pallet_registry \ - --extrinsic="*" \ - --output=pallets/registry/src/weights.rs \ - --template=./.maintain/frame-weight-template.hbs \ No newline at end of file +./target/production/node-subtensor benchmark pallet \ + --chain=local \ + --pallet=pallet_registry \ + --extrinsic="*" \ + --output=pallets/registry/src/weights.rs \ + --template=./.maintain/frame-weight-template.hbs diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 52b74778a..52bdaf2c5 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -1,49 +1,46 @@ #!/usr/bin/env bash - -DEFAULT_BIN_PATH='./target/release/node-subtensor' +DEFAULT_BIN_PATH='./target/production/node-subtensor' BIN_PATH=$DEFAULT_BIN_PATH TMP_SPEC='temp.json' OUTPUT_FILE='benchmarking.txt' - # Getting arguments from user while [[ $# -gt 0 ]]; do case $1 in - -p|--bin-path) - BIN_PATH="$2" - shift - shift - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") - shift - ;; + -p | --bin-path) + BIN_PATH="$2" + shift + shift + ;; + -* | --*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + shift + ;; esac done # Ensure binary exists before node-subtensor executions if [ ! -f $BIN_PATH ]; then - if [[ "$DEFAULT_BIN_PATH" == "$BIN_PATH" ]]; then - cargo build --release --features runtime-benchmarks - else - echo "Binary '$BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." - exit 1 - fi + if [[ "$DEFAULT_BIN_PATH" == "$BIN_PATH" ]]; then + cargo build --profile production --features runtime-benchmarks + else + echo "Binary '$BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." + exit 1 + fi fi # Build Temporary Spec -$BIN_PATH build-spec --disable-default-bootnode --raw --chain local > $TMP_SPEC +$BIN_PATH build-spec --disable-default-bootnode --raw --chain local >$TMP_SPEC # Run benchmark $BIN_PATH benchmark pallet \ - --chain=$TMP_SPEC \ - --execution=native --wasm-execution=compiled \ - --pallet pallet-subtensor --extrinsic 'benchmark_dissolve_network' \ - --output $OUTPUT_FILE + --chain=$TMP_SPEC \ + --pallet pallet-subtensor --extrinsic 'benchmark_dissolve_network' \ + --output $OUTPUT_FILE -rm $TMP_SPEC \ No newline at end of file +rm $TMP_SPEC diff --git a/scripts/build.sh b/scripts/build.sh index b3f63171b..548af664b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1 +1,2 @@ -cargo build --release --features runtime-benchmarks \ No newline at end of file +cargo build --profile production --features runtime-benchmarks + diff --git a/scripts/run/subtensor.sh b/scripts/run/subtensor.sh index e8fd3d58c..015196b29 100755 --- a/scripts/run/subtensor.sh +++ b/scripts/run/subtensor.sh @@ -4,125 +4,123 @@ # Helper functions # -function run_command() -{ - F_NETWORK=$1 - F_NODE_TYPE=$2 - F_BIN_PATH=$3 +function run_command() { + F_NETWORK=$1 + F_NODE_TYPE=$2 + F_BIN_PATH=$3 - # Different command options by network and node type - MAINNET_BOOTNODE='--bootnodes /dns/bootnode.finney.chain.opentensor.ai/tcp/30333/ws/p2p/12D3KooWRwbMb85RWnT8DSXSYMWQtuDwh4LJzndoRrTDotTR5gDC' - TESTNET_BOOTNODE='--bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/ws/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr' - NODE_TYPE_ARCHIVE='--pruning=archive' - NODE_TYPE_LITE='--sync warp' + # Different command options by network and node type + MAINNET_BOOTNODE='--bootnodes /dns/bootnode.finney.chain.opentensor.ai/tcp/30333/ws/p2p/12D3KooWRwbMb85RWnT8DSXSYMWQtuDwh4LJzndoRrTDotTR5gDC' + TESTNET_BOOTNODE='--bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/ws/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr' + NODE_TYPE_ARCHIVE='--pruning=archive' + NODE_TYPE_LITE='--sync warp' - # Options by the type of node we offer - MAINNET_ARCHIVE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_ARCHIVE" - MAINNET_LITE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_LITE" - TESTNET_ARCHIVE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_ARCHIVE" - TESTNET_LITE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_LITE" + # Options by the type of node we offer + MAINNET_ARCHIVE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_ARCHIVE" + MAINNET_LITE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_LITE" + TESTNET_ARCHIVE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_ARCHIVE" + TESTNET_LITE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_LITE" - # Checking options to use - if [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then - SPECIFIC_OPTIONS=$MAINNET_ARCHIVE_OPTIONS - elif [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then - SPECIFIC_OPTIONS=$MAINNET_LITE_OPTIONS - elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then - SPECIFIC_OPTIONS=$TESTNET_ARCHIVE_OPTIONS - elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then - SPECIFIC_OPTIONS=$TESTNET_LITE_OPTIONS - fi + # Checking options to use + if [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then + SPECIFIC_OPTIONS=$MAINNET_ARCHIVE_OPTIONS + elif [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then + SPECIFIC_OPTIONS=$MAINNET_LITE_OPTIONS + elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then + SPECIFIC_OPTIONS=$TESTNET_ARCHIVE_OPTIONS + elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then + SPECIFIC_OPTIONS=$TESTNET_LITE_OPTIONS + fi - if [ ! -f $F_BIN_PATH ]; then - echo "Binary '$F_BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." - echo "Please ensure you have compiled the binary first." - exit 1 - fi + if [ ! -f $F_BIN_PATH ]; then + echo "Binary '$F_BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." + echo "Please ensure you have compiled the binary first." + exit 1 + fi - # Command to run subtensor - $F_BIN_PATH \ - --base-path /tmp/blockchain \ - --chain ./raw_spec.json \ - --rpc-external --rpc-cors all \ - --no-mdns \ - --rpc-max-connections 10000 --in-peers 500 --out-peers 500 \ - $SPECIFIC_OPTIONS + # Command to run subtensor + $F_BIN_PATH \ + --base-path /tmp/blockchain \ + --chain ./raw_spec.json \ + --rpc-external --rpc-cors all \ + --no-mdns \ + --rpc-max-connections 10000 --in-peers 500 --out-peers 500 \ + $SPECIFIC_OPTIONS } - # Default values EXEC_TYPE="docker" NETWORK="mainnet" NODE_TYPE="lite" BUILD="" -BIN_PATH="./target/release/node-subtensor" +BIN_PATH="./target/production/node-subtensor" # Getting arguments from user while [[ $# -gt 0 ]]; do case $1 in - -h|--help) - help - exit 0 - ;; - -e|--execution) - EXEC_TYPE="$2" - shift # past argument - shift # past value - ;; - -b|--build) - BUILD="--build" - shift # past argument - ;; - -n|--network) - NETWORK="$2" - shift - shift - ;; - -t|--node-type) - NODE_TYPE="$2" - shift - shift - ;; - -p|--bin-path) - BIN_PATH="$2" - shift - shift - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") - shift - ;; + -h | --help) + help + exit 0 + ;; + -e | --execution) + EXEC_TYPE="$2" + shift # past argument + shift # past value + ;; + -b | --build) + BUILD="--build" + shift # past argument + ;; + -n | --network) + NETWORK="$2" + shift + shift + ;; + -t | --node-type) + NODE_TYPE="$2" + shift + shift + ;; + -p | --bin-path) + BIN_PATH="$2" + shift + shift + ;; + -* | --*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + shift + ;; esac done # Verifying arguments values if ! [[ "$EXEC_TYPE" =~ ^(docker|binary)$ ]]; then - echo "Exec type not expected: $EXEC_TYPE" - exit 1 + echo "Exec type not expected: $EXEC_TYPE" + exit 1 fi if ! [[ "$NETWORK" =~ ^(mainnet|testnet)$ ]]; then - echo "Network not expected: $NETWORK" - exit 1 + echo "Network not expected: $NETWORK" + exit 1 fi if ! [[ "$NODE_TYPE" =~ ^(lite|archive)$ ]]; then - echo "Node type not expected: $NODE_TYPE" - exit 1 + echo "Node type not expected: $NODE_TYPE" + exit 1 fi # Running subtensor case $EXEC_TYPE in - docker) - docker compose down --remove-orphans - echo "Running docker compose up $BUILD --detach $NETWORK-$NODE_TYPE" - docker compose up $BUILD --detach $NETWORK-$NODE_TYPE - ;; - binary) - run_command $NETWORK $NODE_TYPE $BIN_PATH - ;; +docker) + docker compose down --remove-orphans + echo "Running docker compose up $BUILD --detach $NETWORK-$NODE_TYPE" + docker compose up $BUILD --detach $NETWORK-$NODE_TYPE + ;; +binary) + run_command $NETWORK $NODE_TYPE $BIN_PATH + ;; esac From f6608b8a0ebf7c5ece6eafa70196c2ce33af90bb Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 25 Jun 2024 19:19:38 +0800 Subject: [PATCH 010/185] update one error comment --- pallets/subtensor/src/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index 62c1d799b..f99f28419 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -64,7 +64,7 @@ mod errors { MaxWeightExceeded, /// The hotkey is attempting to become a delegate when the hotkey is already a delegate. HotKeyAlreadyDelegate, - /// The hotkey is attempting to set weights twice within the duration of net_tempo/2 blocks. + /// A transactor exceeded the rate limit for setting weights. SettingWeightsTooFast, /// A validator is attempting to set weights from a validator with incorrect weight version. IncorrectWeightVersionKey, From 15878cb819c1ff2b8d82c5fea8b40244331e42a8 Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 25 Jun 2024 14:49:52 +0300 Subject: [PATCH 011/185] update spec file usage --- docker-compose.yml | 10 +- docs/running-subtensor-locally.md | 9 +- scripts/run/subtensor.sh | 176 +++++++++++++++--------------- 3 files changed, 97 insertions(+), 98 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7b76cd053..09638c174 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: "3.8" volumes: mainnet-lite-volume: @@ -38,7 +38,7 @@ services: - | node-subtensor \ --base-path /tmp/blockchain \ - --chain raw_spec.json \ + --chain raw_spec_finney.json \ --rpc-external --rpc-cors all \ --no-mdns \ --in-peers 500 --out-peers 500 \ @@ -56,7 +56,7 @@ services: - | node-subtensor \ --base-path /tmp/blockchain \ - --chain raw_spec.json \ + --chain raw_spec_finney.json \ --rpc-external --rpc-cors all \ --no-mdns \ --in-peers 500 --out-peers 500 \ @@ -74,7 +74,7 @@ services: - | node-subtensor \ --base-path /tmp/blockchain \ - --chain raw_testspec.json \ + --chain raw_spec_testfinney.json \ --rpc-external --rpc-cors all \ --no-mdns \ --in-peers 500 --out-peers 500 \ @@ -94,7 +94,7 @@ services: - | node-subtensor \ --base-path /tmp/blockchain \ - --chain raw_testspec.json \ + --chain raw_spec_testfinney.json \ --rpc-external --rpc-cors all \ --no-mdns \ --in-peers 500 --out-peers 500 \ diff --git a/docs/running-subtensor-locally.md b/docs/running-subtensor-locally.md index 4d827d858..bc0fe25ff 100644 --- a/docs/running-subtensor-locally.md +++ b/docs/running-subtensor-locally.md @@ -174,7 +174,7 @@ You can now run the public subtensor node either as a lite node or as an archive To run a lite node connected to the mainchain, execute the below command (note the `--sync=warp` flag which runs the subtensor node in lite mode): ```bash title="With --sync=warp setting, for lite node" -./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_spec_finney.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Archive node on mainchain @@ -182,7 +182,7 @@ To run a lite node connected to the mainchain, execute the below command (note t To run an archive node connected to the mainchain, execute the below command (note the `--sync=full` which syncs the node to the full chain and `--pruning archive` flags, which disables the node's automatic pruning of older historical data): ```bash title="With --sync=full and --pruning archive setting, for archive node" -./target/release/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_spec_finney.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Lite node on testchain @@ -190,7 +190,7 @@ To run an archive node connected to the mainchain, execute the below command (no To run a lite node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=warp setting, for lite node." -./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_spec_testfinney.json --base-path /tmp/blockchain --sync=warp --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ### Archive node on testchain @@ -198,8 +198,9 @@ To run a lite node connected to the testchain, execute the below command: To run an archive node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=full and --pruning archive setting, for archive node" -./target/release/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +./target/release/node-subtensor --chain raw_spec_testfinney.json --base-path /tmp/blockchain --sync=full --pruning archive --execution wasm --wasm-execution compiled --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external ``` ## Running on cloud + We have not tested these installation scripts on any cloud service. In addition, if you are using Runpod cloud service, then note that this service is already [containerized](https://docs.runpod.io/pods/overview). Hence, the only option available to you is to compile from the source, as described in the above [Method 2: Using Source Code](#method-2-using-source-code) section. Note that these scripts have not been tested on Runpod. diff --git a/scripts/run/subtensor.sh b/scripts/run/subtensor.sh index e8fd3d58c..b28e8dd6f 100755 --- a/scripts/run/subtensor.sh +++ b/scripts/run/subtensor.sh @@ -4,52 +4,50 @@ # Helper functions # -function run_command() -{ - F_NETWORK=$1 - F_NODE_TYPE=$2 - F_BIN_PATH=$3 +function run_command() { + F_NETWORK=$1 + F_NODE_TYPE=$2 + F_BIN_PATH=$3 - # Different command options by network and node type - MAINNET_BOOTNODE='--bootnodes /dns/bootnode.finney.chain.opentensor.ai/tcp/30333/ws/p2p/12D3KooWRwbMb85RWnT8DSXSYMWQtuDwh4LJzndoRrTDotTR5gDC' - TESTNET_BOOTNODE='--bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/ws/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr' - NODE_TYPE_ARCHIVE='--pruning=archive' - NODE_TYPE_LITE='--sync warp' + # Different command options by network and node type + MAINNET_BOOTNODE='--bootnodes /dns/bootnode.finney.chain.opentensor.ai/tcp/30333/ws/p2p/12D3KooWRwbMb85RWnT8DSXSYMWQtuDwh4LJzndoRrTDotTR5gDC' + TESTNET_BOOTNODE='--bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/ws/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr' + NODE_TYPE_ARCHIVE='--pruning=archive' + NODE_TYPE_LITE='--sync warp' - # Options by the type of node we offer - MAINNET_ARCHIVE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_ARCHIVE" - MAINNET_LITE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_LITE" - TESTNET_ARCHIVE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_ARCHIVE" - TESTNET_LITE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_LITE" + # Options by the type of node we offer + MAINNET_ARCHIVE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_ARCHIVE" + MAINNET_LITE_OPTIONS="$MAINNET_BOOTNODE $NODE_TYPE_LITE" + TESTNET_ARCHIVE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_ARCHIVE" + TESTNET_LITE_OPTIONS="$TESTNET_BOOTNODE $NODE_TYPE_LITE" - # Checking options to use - if [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then - SPECIFIC_OPTIONS=$MAINNET_ARCHIVE_OPTIONS - elif [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then - SPECIFIC_OPTIONS=$MAINNET_LITE_OPTIONS - elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then - SPECIFIC_OPTIONS=$TESTNET_ARCHIVE_OPTIONS - elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then - SPECIFIC_OPTIONS=$TESTNET_LITE_OPTIONS - fi + # Checking options to use + if [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then + SPECIFIC_OPTIONS=$MAINNET_ARCHIVE_OPTIONS + elif [[ "$F_NETWORK" == "mainnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then + SPECIFIC_OPTIONS=$MAINNET_LITE_OPTIONS + elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "archive" ]]; then + SPECIFIC_OPTIONS=$TESTNET_ARCHIVE_OPTIONS + elif [[ "$F_NETWORK" == "testnet" ]] && [[ "$F_NODE_TYPE" == "lite" ]]; then + SPECIFIC_OPTIONS=$TESTNET_LITE_OPTIONS + fi - if [ ! -f $F_BIN_PATH ]; then - echo "Binary '$F_BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." - echo "Please ensure you have compiled the binary first." - exit 1 - fi + if [ ! -f $F_BIN_PATH ]; then + echo "Binary '$F_BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." + echo "Please ensure you have compiled the binary first." + exit 1 + fi - # Command to run subtensor - $F_BIN_PATH \ - --base-path /tmp/blockchain \ - --chain ./raw_spec.json \ - --rpc-external --rpc-cors all \ - --no-mdns \ - --rpc-max-connections 10000 --in-peers 500 --out-peers 500 \ - $SPECIFIC_OPTIONS + # Command to run subtensor + $F_BIN_PATH \ + --base-path /tmp/blockchain \ + --chain ./raw_spec_finney.json \ + --rpc-external --rpc-cors all \ + --no-mdns \ + --rpc-max-connections 10000 --in-peers 500 --out-peers 500 \ + $SPECIFIC_OPTIONS } - # Default values EXEC_TYPE="docker" NETWORK="mainnet" @@ -60,69 +58,69 @@ BIN_PATH="./target/release/node-subtensor" # Getting arguments from user while [[ $# -gt 0 ]]; do case $1 in - -h|--help) - help - exit 0 - ;; - -e|--execution) - EXEC_TYPE="$2" - shift # past argument - shift # past value - ;; - -b|--build) - BUILD="--build" - shift # past argument - ;; - -n|--network) - NETWORK="$2" - shift - shift - ;; - -t|--node-type) - NODE_TYPE="$2" - shift - shift - ;; - -p|--bin-path) - BIN_PATH="$2" - shift - shift - ;; - -*|--*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") - shift - ;; + -h | --help) + help + exit 0 + ;; + -e | --execution) + EXEC_TYPE="$2" + shift # past argument + shift # past value + ;; + -b | --build) + BUILD="--build" + shift # past argument + ;; + -n | --network) + NETWORK="$2" + shift + shift + ;; + -t | --node-type) + NODE_TYPE="$2" + shift + shift + ;; + -p | --bin-path) + BIN_PATH="$2" + shift + shift + ;; + -* | --*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + shift + ;; esac done # Verifying arguments values if ! [[ "$EXEC_TYPE" =~ ^(docker|binary)$ ]]; then - echo "Exec type not expected: $EXEC_TYPE" - exit 1 + echo "Exec type not expected: $EXEC_TYPE" + exit 1 fi if ! [[ "$NETWORK" =~ ^(mainnet|testnet)$ ]]; then - echo "Network not expected: $NETWORK" - exit 1 + echo "Network not expected: $NETWORK" + exit 1 fi if ! [[ "$NODE_TYPE" =~ ^(lite|archive)$ ]]; then - echo "Node type not expected: $NODE_TYPE" - exit 1 + echo "Node type not expected: $NODE_TYPE" + exit 1 fi # Running subtensor case $EXEC_TYPE in - docker) - docker compose down --remove-orphans - echo "Running docker compose up $BUILD --detach $NETWORK-$NODE_TYPE" - docker compose up $BUILD --detach $NETWORK-$NODE_TYPE - ;; - binary) - run_command $NETWORK $NODE_TYPE $BIN_PATH - ;; +docker) + docker compose down --remove-orphans + echo "Running docker compose up $BUILD --detach $NETWORK-$NODE_TYPE" + docker compose up $BUILD --detach $NETWORK-$NODE_TYPE + ;; +binary) + run_command $NETWORK $NODE_TYPE $BIN_PATH + ;; esac From 2eb1fe22070907882d3fd89028b69db2a52fd5b8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 25 Jun 2024 23:10:00 -0400 Subject: [PATCH 012/185] always run e2e tests on all pull requests and on push for major branches --- .github/workflows/e2e-bittensor-tests.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/e2e-bittensor-tests.yml b/.github/workflows/e2e-bittensor-tests.yml index 773edb566..d21514909 100644 --- a/.github/workflows/e2e-bittensor-tests.yml +++ b/.github/workflows/e2e-bittensor-tests.yml @@ -5,16 +5,10 @@ concurrency: cancel-in-progress: true on: - ## Run automatically for all PRs against main, regardless of what the changes are - ## to be safe and so we can more easily force re-run the CI when github is being - ## weird by using a blank commit push: - branches: [main, development, staging] + branches: [main, devnet-ready, devnet, testnet, finney] - ## - # Run automatically for PRs against default/main branch if Rust files change pull_request: - branches: [main, development, staging] ## Allow running workflow manually from the Actions tab workflow_dispatch: From 721ee94a9e7f7c074f11d3699e2da3e3415d81a8 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 1 Jul 2024 17:20:50 +0400 Subject: [PATCH 013/185] fix: run emissions when reg is off --- pallets/subtensor/src/block_step.rs | 76 ++++++++++++++------------- pallets/subtensor/tests/block_step.rs | 39 ++++++++++++++ 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/pallets/subtensor/src/block_step.rs b/pallets/subtensor/src/block_step.rs index 80733e6b7..54713533c 100644 --- a/pallets/subtensor/src/block_step.rs +++ b/pallets/subtensor/src/block_step.rs @@ -27,9 +27,9 @@ impl Pallet { Ok(()) } - /// Helper function which returns the number of blocks remaining before we will run the epoch on this - /// network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 - /// + // Helper function which returns the number of blocks remaining before we will run the epoch on this + // network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 + // pub fn blocks_until_next_epoch(netuid: u16, tempo: u16, block_number: u64) -> u64 { // tempo | netuid | # first epoch block // 1 0 0 @@ -45,9 +45,9 @@ impl Pallet { tempo as u64 - (block_number + netuid as u64 + 1) % (tempo as u64 + 1) } - /// Helper function returns the number of tuples to drain on a particular step based on - /// the remaining tuples to sink and the block number - /// + // Helper function returns the number of tuples to drain on a particular step based on + // the remaining tuples to sink and the block number + // pub fn tuples_to_drain_this_block( netuid: u16, tempo: u16, @@ -78,9 +78,9 @@ impl Pallet { LoadedEmission::::get(netuid) } - /// Reads from the loaded emission storage which contains lists of pending emission tuples ( hotkey, amount ) - /// and distributes small chunks of them at a time. - /// + // Reads from the loaded emission storage which contains lists of pending emission tuples ( hotkey, amount ) + // and distributes small chunks of them at a time. + // pub fn drain_emission(_: u64) { // --- 1. We iterate across each network. for (netuid, _) in as IterableStorageMap>::iter() { @@ -102,21 +102,25 @@ impl Pallet { } } - /// Iterates through networks queues more emission onto their pending storage. - /// If a network has no blocks left until tempo, we run the epoch function and generate - /// more token emission tuples for later draining onto accounts. - /// + // Iterates through networks queues more emission onto their pending storage. + // If a network has no blocks left until tempo, we run the epoch function and generate + // more token emission tuples for later draining onto accounts. + // pub fn generate_emission(block_number: u64) { // --- 1. Iterate across each network and add pending emission into stash. for (netuid, tempo) in as IterableStorageMap>::iter() { // Skip the root network or subnets with registrations turned off - if netuid == Self::get_root_netuid() || !Self::is_registration_allowed(netuid) { + if netuid == Self::get_root_netuid() { // Root emission or subnet emission is burned continue; } // --- 2. Queue the emission due to this network. - let new_queued_emission: u64 = Self::get_subnet_emission_value(netuid); + let mut new_queued_emission: u64 = Self::get_subnet_emission_value(netuid); + if !Self::is_registration_allowed(netuid) { + new_queued_emission = 0; // No emission for this network if registration is off. + } + log::debug!( "generate_emission for netuid: {:?} with tempo: {:?} and emission: {:?}", netuid, @@ -196,10 +200,10 @@ impl Pallet { Self::set_last_mechanism_step_block(netuid, block_number); } } - /// Distributes token inflation through the hotkey based on emission. The call ensures that the inflation - /// is distributed onto the accounts in proportion of the stake delegated minus the take. This function - /// is called after an epoch to distribute the newly minted stake according to delegation. - /// + // Distributes token inflation through the hotkey based on emission. The call ensures that the inflation + // is distributed onto the accounts in proportion of the stake delegated minus the take. This function + // is called after an epoch to distribute the newly minted stake according to delegation. + // pub fn emit_inflation_through_hotkey_account( hotkey: &T::AccountId, server_emission: u64, @@ -260,9 +264,9 @@ impl Pallet { Self::increase_stake_on_hotkey_account(hotkey, server_emission); } - /// Increases the stake on the cold - hot pairing by increment while also incrementing other counters. - /// This function should be called rather than set_stake under account. - /// + // Increases the stake on the cold - hot pairing by increment while also incrementing other counters. + // This function should be called rather than set_stake under account. + // pub fn block_step_increase_stake_on_coldkey_hotkey_account( coldkey: &T::AccountId, hotkey: &T::AccountId, @@ -281,8 +285,8 @@ impl Pallet { TotalStake::::put(TotalStake::::get().saturating_add(increment)); } - /// Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. - /// + // Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. + // pub fn block_step_decrease_stake_on_coldkey_hotkey_account( coldkey: &T::AccountId, hotkey: &T::AccountId, @@ -301,8 +305,8 @@ impl Pallet { TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); } - /// Returns emission awarded to a hotkey as a function of its proportion of the total stake. - /// + // Returns emission awarded to a hotkey as a function of its proportion of the total stake. + // pub fn calculate_stake_proportional_emission( stake: u64, total_stake: u64, @@ -316,8 +320,8 @@ impl Pallet { proportional_emission.to_num::() } - /// Returns the delegated stake 'take' assigned to this key. (If exists, otherwise 0) - /// + // Returns the delegated stake 'take' assigned to this key. (If exists, otherwise 0) + // pub fn calculate_delegate_proportional_take(hotkey: &T::AccountId, emission: u64) -> u64 { if Self::hotkey_is_delegate(hotkey) { let take_proportion: I64F64 = @@ -329,8 +333,8 @@ impl Pallet { } } - /// Adjusts the network difficulties/burns of every active network. Resetting state parameters. - /// + // Adjusts the network difficulties/burns of every active network. Resetting state parameters. + // pub fn adjust_registration_terms_for_networks() { log::debug!("adjust_registration_terms_for_networks"); @@ -486,9 +490,9 @@ impl Pallet { } } - /// Calculates the upgraded difficulty by multiplying the current difficulty by the ratio ( reg_actual + reg_target / reg_target + reg_target ) - /// We use I110F18 to avoid any overflows on u64. Also min_difficulty and max_difficulty bound the range. - /// + // Calculates the upgraded difficulty by multiplying the current difficulty by the ratio ( reg_actual + reg_target / reg_target + reg_target ) + // We use I110F18 to avoid any overflows on u64. Also min_difficulty and max_difficulty bound the range. + // pub fn upgraded_difficulty( netuid: u16, current_difficulty: u64, @@ -513,9 +517,9 @@ impl Pallet { } } - /// Calculates the upgraded burn by multiplying the current burn by the ratio ( reg_actual + reg_target / reg_target + reg_target ) - /// We use I110F18 to avoid any overflows on u64. Also min_burn and max_burn bound the range. - /// + // Calculates the upgraded burn by multiplying the current burn by the ratio ( reg_actual + reg_target / reg_target + reg_target ) + // We use I110F18 to avoid any overflows on u64. Also min_burn and max_burn bound the range. + // pub fn upgraded_burn( netuid: u16, current_burn: u64, diff --git a/pallets/subtensor/tests/block_step.rs b/pallets/subtensor/tests/block_step.rs index 5df173906..502362d70 100644 --- a/pallets/subtensor/tests/block_step.rs +++ b/pallets/subtensor/tests/block_step.rs @@ -895,3 +895,42 @@ fn test_emission_based_on_registration_status() { ); }); } + +#[test] +fn test_epoch_runs_when_registration_disabled() { + new_test_ext(1).execute_with(|| { + let n: u16 = 100; + let netuid_off: u16 = 1; + let tempo: u16 = 1; + let netuids: Vec = vec![netuid_off]; + let emissions: Vec = vec![1000000000]; + + // Add subnets with registration turned off and on + add_network(netuid_off, tempo, 0); + SubtensorModule::set_max_allowed_uids(netuid_off, n); + SubtensorModule::set_emission_values(&netuids, emissions).unwrap(); + SubtensorModule::set_network_registration_allowed(netuid_off, false); + + // Populate the subnets with neurons + for i in 0..n { + SubtensorModule::append_neuron(netuid_off, &U256::from(i), 0); + } + + // Generate emission at block 1 + let block: u64 = 1; + SubtensorModule::generate_emission(block); + + step_block(1); // Now block 2 + + // Verify blocks since last step was set + assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid_off), 1); + + // Step to the next epoch block + let epoch_block: u16 = tempo; + step_block(epoch_block); + + // Verify blocks since last step was set, this indicates we ran the epoch + assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid_off), 0 as u64); + assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_some()); + }); +} \ No newline at end of file From a8b71e90fb45db7d3eb3c520121020b3a982d911 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 1 Jul 2024 17:23:15 +0400 Subject: [PATCH 014/185] chore: bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2364008fd..5564dca5d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -135,7 +135,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 152, + spec_version: 153, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 59b067970c366024146766acba257d09df27b6c1 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 1 Jul 2024 17:29:14 +0400 Subject: [PATCH 015/185] chore: add back rust docs --- pallets/subtensor/src/block_step.rs | 68 ++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/pallets/subtensor/src/block_step.rs b/pallets/subtensor/src/block_step.rs index 54713533c..091ccd736 100644 --- a/pallets/subtensor/src/block_step.rs +++ b/pallets/subtensor/src/block_step.rs @@ -27,9 +27,9 @@ impl Pallet { Ok(()) } - // Helper function which returns the number of blocks remaining before we will run the epoch on this - // network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 - // + /// Helper function which returns the number of blocks remaining before we will run the epoch on this + /// network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 + /// pub fn blocks_until_next_epoch(netuid: u16, tempo: u16, block_number: u64) -> u64 { // tempo | netuid | # first epoch block // 1 0 0 @@ -45,9 +45,9 @@ impl Pallet { tempo as u64 - (block_number + netuid as u64 + 1) % (tempo as u64 + 1) } - // Helper function returns the number of tuples to drain on a particular step based on - // the remaining tuples to sink and the block number - // + /// Helper function returns the number of tuples to drain on a particular step based on + /// the remaining tuples to sink and the block number + /// pub fn tuples_to_drain_this_block( netuid: u16, tempo: u16, @@ -78,9 +78,9 @@ impl Pallet { LoadedEmission::::get(netuid) } - // Reads from the loaded emission storage which contains lists of pending emission tuples ( hotkey, amount ) - // and distributes small chunks of them at a time. - // + /// Reads from the loaded emission storage which contains lists of pending emission tuples ( hotkey, amount ) + /// and distributes small chunks of them at a time. + /// pub fn drain_emission(_: u64) { // --- 1. We iterate across each network. for (netuid, _) in as IterableStorageMap>::iter() { @@ -102,10 +102,10 @@ impl Pallet { } } - // Iterates through networks queues more emission onto their pending storage. - // If a network has no blocks left until tempo, we run the epoch function and generate - // more token emission tuples for later draining onto accounts. - // + /// Iterates through networks queues more emission onto their pending storage. + /// If a network has no blocks left until tempo, we run the epoch function and generate + /// more token emission tuples for later draining onto accounts. + /// pub fn generate_emission(block_number: u64) { // --- 1. Iterate across each network and add pending emission into stash. for (netuid, tempo) in as IterableStorageMap>::iter() { @@ -200,10 +200,10 @@ impl Pallet { Self::set_last_mechanism_step_block(netuid, block_number); } } - // Distributes token inflation through the hotkey based on emission. The call ensures that the inflation - // is distributed onto the accounts in proportion of the stake delegated minus the take. This function - // is called after an epoch to distribute the newly minted stake according to delegation. - // + /// Distributes token inflation through the hotkey based on emission. The call ensures that the inflation + /// is distributed onto the accounts in proportion of the stake delegated minus the take. This function + /// is called after an epoch to distribute the newly minted stake according to delegation. + /// pub fn emit_inflation_through_hotkey_account( hotkey: &T::AccountId, server_emission: u64, @@ -264,9 +264,9 @@ impl Pallet { Self::increase_stake_on_hotkey_account(hotkey, server_emission); } - // Increases the stake on the cold - hot pairing by increment while also incrementing other counters. - // This function should be called rather than set_stake under account. - // + /// Increases the stake on the cold - hot pairing by increment while also incrementing other counters. + /// This function should be called rather than set_stake under account. + /// pub fn block_step_increase_stake_on_coldkey_hotkey_account( coldkey: &T::AccountId, hotkey: &T::AccountId, @@ -285,8 +285,8 @@ impl Pallet { TotalStake::::put(TotalStake::::get().saturating_add(increment)); } - // Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. - // + /// Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. + /// pub fn block_step_decrease_stake_on_coldkey_hotkey_account( coldkey: &T::AccountId, hotkey: &T::AccountId, @@ -305,8 +305,8 @@ impl Pallet { TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); } - // Returns emission awarded to a hotkey as a function of its proportion of the total stake. - // + /// Returns emission awarded to a hotkey as a function of its proportion of the total stake. + /// pub fn calculate_stake_proportional_emission( stake: u64, total_stake: u64, @@ -320,8 +320,8 @@ impl Pallet { proportional_emission.to_num::() } - // Returns the delegated stake 'take' assigned to this key. (If exists, otherwise 0) - // + /// Returns the delegated stake 'take' assigned to this key. (If exists, otherwise 0) + /// pub fn calculate_delegate_proportional_take(hotkey: &T::AccountId, emission: u64) -> u64 { if Self::hotkey_is_delegate(hotkey) { let take_proportion: I64F64 = @@ -333,8 +333,8 @@ impl Pallet { } } - // Adjusts the network difficulties/burns of every active network. Resetting state parameters. - // + /// Adjusts the network difficulties/burns of every active network. Resetting state parameters. + /// pub fn adjust_registration_terms_for_networks() { log::debug!("adjust_registration_terms_for_networks"); @@ -490,9 +490,9 @@ impl Pallet { } } - // Calculates the upgraded difficulty by multiplying the current difficulty by the ratio ( reg_actual + reg_target / reg_target + reg_target ) - // We use I110F18 to avoid any overflows on u64. Also min_difficulty and max_difficulty bound the range. - // + /// Calculates the upgraded difficulty by multiplying the current difficulty by the ratio ( reg_actual + reg_target / reg_target + reg_target ) + /// We use I110F18 to avoid any overflows on u64. Also min_difficulty and max_difficulty bound the range. + /// pub fn upgraded_difficulty( netuid: u16, current_difficulty: u64, @@ -517,9 +517,9 @@ impl Pallet { } } - // Calculates the upgraded burn by multiplying the current burn by the ratio ( reg_actual + reg_target / reg_target + reg_target ) - // We use I110F18 to avoid any overflows on u64. Also min_burn and max_burn bound the range. - // + /// Calculates the upgraded burn by multiplying the current burn by the ratio ( reg_actual + reg_target / reg_target + reg_target ) + /// We use I110F18 to avoid any overflows on u64. Also min_burn and max_burn bound the range. + /// pub fn upgraded_burn( netuid: u16, current_burn: u64, From 43f0402990a674d14e7a6b16c43fc1522005b243 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 1 Jul 2024 18:09:25 +0400 Subject: [PATCH 016/185] chore: lints --- justfile | 6 +++--- pallets/subtensor/tests/block_step.rs | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/justfile b/justfile index 691d5bca2..495f78a27 100644 --- a/justfile +++ b/justfile @@ -4,7 +4,7 @@ export RUST_BACKTRACE := "full" export SKIP_WASM_BUILD := "1" export RUST_BIN_DIR := "target/x86_64-unknown-linux-gnu" export TARGET := "x86_64-unknown-linux-gnu" -export RUSTV := "nightly-2024-03-05" +export RUSTV := "stable" export RELEASE_NAME := "development" fmt: @@ -25,13 +25,13 @@ benchmarks: clippy: @echo "Running cargo clippy..." - cargo +{{RUSTV}} clippy -- -D clippy::panic \ + cargo +{{RUSTV}} clippy --workspace --all-targets -- -D \ -D clippy::todo \ -D clippy::unimplemented clippy-fix: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." - cargo +{{RUSTV}} clippy --fix --allow-dirty -- -A clippy::panic \ + cargo +{{RUSTV}} clippy --fix --allow-dirty --workspace --all-targets -- -A \ -A clippy::todo \ -A clippy::unimplemented fix: diff --git a/pallets/subtensor/tests/block_step.rs b/pallets/subtensor/tests/block_step.rs index 502362d70..9c81492e9 100644 --- a/pallets/subtensor/tests/block_step.rs +++ b/pallets/subtensor/tests/block_step.rs @@ -920,17 +920,20 @@ fn test_epoch_runs_when_registration_disabled() { let block: u64 = 1; SubtensorModule::generate_emission(block); - step_block(1); // Now block 2 + step_block(1); // Now block 2 - // Verify blocks since last step was set - assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid_off), 1); + // Verify blocks since last step was set + assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid_off), 1); // Step to the next epoch block let epoch_block: u16 = tempo; step_block(epoch_block); - // Verify blocks since last step was set, this indicates we ran the epoch - assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid_off), 0 as u64); - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_some()); + // Verify blocks since last step was set, this indicates we ran the epoch + assert_eq!( + SubtensorModule::get_blocks_since_last_step(netuid_off), + 0_u64 + ); + assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_some()); }); -} \ No newline at end of file +} From 36a1aefa249b08d737a26852a1606062b5be13c7 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 10:53:31 -0400 Subject: [PATCH 017/185] whoops --- support/macros/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/support/macros/src/lib.rs b/support/macros/src/lib.rs index 778893d7c..97dd76082 100644 --- a/support/macros/src/lib.rs +++ b/support/macros/src/lib.rs @@ -37,6 +37,9 @@ fn freeze_struct_impl( let item = parse2::(tokens)?; let mut item_clone = item.clone(); + let mut visitor = CleanDocComments::new(); + visit_item_struct_mut(&mut visitor, &mut item_clone); + let calculated_hash = generate_hash(&item_clone); let calculated_hash_hex = format!("{:x}", calculated_hash); @@ -50,9 +53,6 @@ fn freeze_struct_impl( let parsed_attr = parse2::(attr)?; let provided_hash_hex = parsed_attr.value().to_lowercase(); - let mut visitor = CleanDocComments::new(); - visit_item_struct_mut(&mut visitor, &mut item_clone); - if provided_hash_hex != calculated_hash_hex { return Err(Error::new_spanned(item, format!( From ac4c3c2eddc562725f1723ce50f505298eac44d8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 11:03:32 -0400 Subject: [PATCH 018/185] fix hash codes --- pallets/collective/src/lib.rs | 2 +- pallets/registry/src/types.rs | 2 +- pallets/subtensor/src/lib.rs | 4 ++-- runtime/src/check_nonce.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 9dbc1f0cb..96040f99c 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -151,7 +151,7 @@ impl GetBacking for RawOrigin { } /// Info for keeping track of a motion being voted on. -#[freeze_struct("5959418cdb31993b")] +#[freeze_struct("a8e7b0b34ad52b17")] #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Votes { /// The proposal's unique index. diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index 3057aeaf4..e5063222e 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -279,7 +279,7 @@ impl TypeInfo for IdentityFields { /// /// NOTE: This should be stored at the end of the storage item to facilitate the addition of extra /// fields in a backwards compatible way through a specialized `Decode` impl. -#[freeze_struct("70b183c8753429f1")] +#[freeze_struct("ac2fc4e3c16c9dc")] #[derive( CloneNoBound, Encode, Decode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index a90f1725b..8b6bd5cce 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -666,7 +666,7 @@ pub mod pallet { pub type AxonInfoOf = AxonInfo; /// Data structure for Axon information. - #[freeze_struct("66109b7ef33baabd")] + #[freeze_struct("3545cfb0cac4c1f5")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct AxonInfo { /// Axon serving block. @@ -690,7 +690,7 @@ pub mod pallet { /// Struct for Prometheus. pub type PrometheusInfoOf = PrometheusInfo; /// Data structure for Prometheus information. - #[freeze_struct("66b04cc1fbd155ea")] + #[freeze_struct("5dde687e63baf0cd")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct PrometheusInfo { /// Prometheus serving block. diff --git a/runtime/src/check_nonce.rs b/runtime/src/check_nonce.rs index 9a31aa038..fd2a3a0db 100644 --- a/runtime/src/check_nonce.rs +++ b/runtime/src/check_nonce.rs @@ -20,7 +20,7 @@ use subtensor_macros::freeze_struct; /// This extension affects `requires` and `provides` tags of validity, but DOES NOT /// set the `priority` field. Make sure that AT LEAST one of the signed extension sets /// some kind of priority upon validating transactions. -#[freeze_struct("91bb250ab490f1b7")] +#[freeze_struct("610b76f62cdb521e")] #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CheckNonce(#[codec(compact)] pub T::Nonce); From 318eec817dcb5b7907ffb40f080336216e9768bd Mon Sep 17 00:00:00 2001 From: orriin <167025436+orriin@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:04:29 +0200 Subject: [PATCH 019/185] Update running-subtensor-locally.md --- docs/running-subtensor-locally.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/running-subtensor-locally.md b/docs/running-subtensor-locally.md index a7aa8c217..bce806fcb 100644 --- a/docs/running-subtensor-locally.md +++ b/docs/running-subtensor-locally.md @@ -182,6 +182,7 @@ To run an archive node connected to the mainchain, execute the below command (no ```bash title="With --sync=full and --pruning archive setting, for archive node" ./target/production/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=full --pruning archive --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +``` ### Lite node on testchain @@ -189,6 +190,7 @@ To run a lite node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=warp setting, for lite node." ./target/production/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=warp --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +``` ### Archive node on testchain @@ -196,6 +198,7 @@ To run an archive node connected to the testchain, execute the below command: ```bash title="With bootnodes set to testnet and --sync=full and --pruning archive setting, for archive node" ./target/production/node-subtensor --chain raw_testspec.json --base-path /tmp/blockchain --sync=full --pruning archive --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /dns/bootnode.test.finney.opentensor.ai/tcp/30333/p2p/12D3KooWPM4mLcKJGtyVtkggqdG84zWrd7Rij6PGQDoijh1X86Vr --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +``` ## Running on cloud From 9051b4b65af8a765a5264de774d70141c6f7088a Mon Sep 17 00:00:00 2001 From: orriin <167025436+orriin@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:04:45 +0200 Subject: [PATCH 020/185] Update running-subtensor-locally.md --- docs/running-subtensor-locally.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/running-subtensor-locally.md b/docs/running-subtensor-locally.md index bce806fcb..505fe2fb5 100644 --- a/docs/running-subtensor-locally.md +++ b/docs/running-subtensor-locally.md @@ -175,6 +175,7 @@ To run a lite node connected to the mainchain, execute the below command (note t ```bash title="With --sync=warp setting, for lite node" ./target/production/node-subtensor --chain raw_spec.json --base-path /tmp/blockchain --sync=warp --port 30333 --max-runtime-instances 32 --rpc-max-response-size 2048 --rpc-cors all --rpc-port 9944 --bootnodes /ip4/13.58.175.193/tcp/30333/p2p/12D3KooWDe7g2JbNETiKypcKT1KsCEZJbTzEHCn8hpd4PHZ6pdz5 --no-mdns --in-peers 8000 --out-peers 8000 --prometheus-external --rpc-external +``` ### Archive node on mainchain From 824d9529d849fde7fde42beb397e55bdbd120416 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 2 Jul 2024 22:13:33 +0400 Subject: [PATCH 021/185] chore: merge conflicts , safe maths --- .gitignore | 5 +- justfile | 16 +++--- pallets/subtensor/src/epoch.rs | 7 +-- pallets/subtensor/src/math.rs | 85 +++++++++++++++++++----------- pallets/subtensor/src/utils.rs | 5 +- pallets/subtensor/tests/math.rs | 5 ++ scripts/specs/local.json | 91 --------------------------------- 7 files changed, 79 insertions(+), 135 deletions(-) delete mode 100644 scripts/specs/local.json diff --git a/.gitignore b/.gitignore index 9cad6e792..f394de80c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ specs/*.json .idea # Runtime upgrade snapshot -bt.snap \ No newline at end of file +bt.snap + +# localnet spec +scripts/specs/local.json \ No newline at end of file diff --git a/justfile b/justfile index e56925047..0a02ee7d3 100644 --- a/justfile +++ b/justfile @@ -25,17 +25,16 @@ benchmarks: clippy: @echo "Running cargo clippy..." - cargo +{{RUSTV}} clippy --workspace --all-targets -- -D \ + cargo +{{RUSTV}} clippy --workspace --all-targets -- \ -D clippy::todo \ -D clippy::unimplemented clippy-fix: - @echo "Running cargo clippy with automatic fixes on potentially dirty code..." - cargo +{{RUSTV}} clippy --fix --allow-dirty --workspace --all-targets -- -A \ - - -A clippy::todo \ - -A clippy::unimplemented \ - -A clippy::indexing_slicing + @echo "Running cargo clippy with automatic fixes on potentially dirty code..." + cargo +{{RUSTV}} clippy --fix --allow-dirty --workspace --all-targets -- \ + -A clippy::todo \ + -A clippy::unimplemented \ + -A clippy::indexing_slicing fix: @echo "Running cargo fix..." cargo +{{RUSTV}} fix --workspace @@ -47,4 +46,5 @@ lint: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." just clippy-fix @echo "Running cargo clippy..." - just clippy \ No newline at end of file + just clippy + diff --git a/pallets/subtensor/src/epoch.rs b/pallets/subtensor/src/epoch.rs index 92d161549..12c407efa 100644 --- a/pallets/subtensor/src/epoch.rs +++ b/pallets/subtensor/src/epoch.rs @@ -531,7 +531,6 @@ impl Pallet { inplace_col_normalize_sparse(&mut bonds_delta, n); // sum_i b_ij = 1 log::trace!("ΔB (norm): {:?}", &bonds_delta); - // Compute the Exponential Moving Average (EMA) of bonds. let mut ema_bonds = Self::compute_ema_bonds_sparse(netuid, consensus.clone(), bonds_delta, bonds); @@ -857,8 +856,10 @@ impl Pallet { // Calculate the intercept 'b' of the logistic function. // b = ln((1 / alpha_low - 1)) + a * consensus_low - let b = safe_ln((I32F32::from_num(1.0) / alpha_low).saturating_sub(I32F32::from_num(1.0))) - .saturating_add(a.saturating_mul(consensus_low)); + let b = safe_ln( + (I32F32::from_num(1.0).saturating_div(alpha_low)).saturating_sub(I32F32::from_num(1.0)), + ) + .saturating_add(a.saturating_mul(consensus_low)); log::trace!("b: {:?}", b); // Return the calculated slope 'a' and intercept 'b'. diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/math.rs index bfb46ea56..1abc9ed9b 100644 --- a/pallets/subtensor/src/math.rs +++ b/pallets/subtensor/src/math.rs @@ -3,7 +3,7 @@ use crate::alloc::borrow::ToOwned; #[allow(unused)] use num_traits::float::Float; -use sp_runtime::traits::CheckedAdd; +use sp_runtime::traits::{CheckedAdd, Saturating}; use sp_std::cmp::Ordering; use sp_std::vec; @@ -1161,17 +1161,20 @@ pub fn mat_ema_alpha_vec_sparse( let n = new.len(); // Assume square matrix, rows=cols let zero: I32F32 = I32F32::from_num(0.0); let mut result: Vec> = vec![vec![]; n]; + // Iterate over each row of the matrices. - for i in 0..new.len() { + for (i, (new_row, old_row)) in new.iter().zip(old).enumerate() { // Initialize a row of zeros for the result matrix. let mut row: Vec = vec![zero; n]; // Process the new matrix values. - for (j, value) in new[i].iter() { + for (j, value) in new_row.iter() { // Retrieve the alpha value for the current column. - let alpha_val: I32F32 = alpha[*j as usize]; - // Compute the EMA component for the new value. - row[*j as usize] = alpha_val * value; + let alpha_val: I32F32 = alpha.get(*j as usize).copied().unwrap_or(zero); + // Compute the EMA component for the new value using saturating multiplication. + if let Some(row_val) = row.get_mut(*j as usize) { + *row_val = alpha_val.saturating_mul(*value); + } log::trace!( "new[{}][{}] * alpha[{}] = {} * {} = {}", i, @@ -1179,18 +1182,20 @@ pub fn mat_ema_alpha_vec_sparse( j, value, alpha_val, - row[*j as usize] + row.get(*j as usize).unwrap_or(&zero) ); } // Process the old matrix values. - for (j, value) in old[i].iter() { + for (j, value) in old_row.iter() { // Retrieve the alpha value for the current column. - let alpha_val: I32F32 = alpha[*j as usize]; - // Calculate the complement of the alpha value. - let one_minus_alpha: I32F32 = I32F32::from_num(1.0) - alpha_val; - // Compute the EMA component for the old value and add it to the row. - row[*j as usize] += one_minus_alpha * value; + let alpha_val: I32F32 = alpha.get(*j as usize).copied().unwrap_or(zero); + // Calculate the complement of the alpha value using saturating subtraction. + let one_minus_alpha: I32F32 = I32F32::from_num(1.0).saturating_sub(alpha_val); + // Compute the EMA component for the old value and add it to the row using saturating operations. + if let Some(row_val) = row.get_mut(*j as usize) { + *row_val = row_val.saturating_add(one_minus_alpha.saturating_mul(*value)); + } log::trace!( "old[{}][{}] * (1 - alpha[{}]) = {} * {} = {}", i, @@ -1198,15 +1203,17 @@ pub fn mat_ema_alpha_vec_sparse( j, value, one_minus_alpha, - one_minus_alpha * value + one_minus_alpha.saturating_mul(*value) ); } // Collect the non-zero values into the result matrix. for (j, value) in row.iter().enumerate() { if *value > zero { - result[i].push((j as u16, *value)); - log::trace!("result[{}][{}] = {}", i, j, value); + if let Some(result_row) = result.get_mut(i) { + result_row.push((j as u16, *value)); + log::trace!("result[{}][{}] = {}", i, j, value); + } } } } @@ -1224,16 +1231,17 @@ pub fn mat_ema_alpha_vec( alpha: &[I32F32], ) -> Vec> { // Check if the new matrix is empty or its first row is empty. - if new.is_empty() || new[0].is_empty() { + if new.is_empty() || new.first().map_or(true, |row| row.is_empty()) { return vec![vec![]; 1]; } // Ensure the dimensions of the new and old matrices match. assert!(new.len() == old.len()); - assert!(new[0].len() == alpha.len()); + assert!(new.first().map_or(0, |row| row.len()) == alpha.len()); // Initialize the result matrix with zeros, having the same dimensions as the new matrix. - let mut result: Vec> = vec![vec![I32F32::from_num(0.0); new[0].len()]; new.len()]; + let mut result: Vec> = + vec![vec![I32F32::from_num(0.0); new.first().map_or(0, |row| row.len())]; new.len()]; // Iterate over each row of the matrices. for (i, (new_row, old_row)) in new.iter().zip(old).enumerate() { @@ -1242,11 +1250,19 @@ pub fn mat_ema_alpha_vec( // Iterate over each column of the current row. for (j, &alpha_val) in alpha.iter().enumerate().take(new_row.len()) { - // Calculate the complement of the alpha value. - let one_minus_alpha = I32F32::from_num(1.0) - alpha_val; - - // Compute the EMA for the current element. - result[i][j] = alpha_val * new_row[j] + one_minus_alpha * old_row[j]; + // Calculate the complement of the alpha value using saturating subtraction. + let one_minus_alpha = I32F32::from_num(1.0).saturating_sub(alpha_val); + + // Compute the EMA for the current element using saturating operations. + if let (Some(new_val), Some(old_val), Some(result_val)) = ( + new_row.get(j), + old_row.get(j), + result.get_mut(i).and_then(|row| row.get_mut(j)), + ) { + *result_val = alpha_val + .saturating_mul(*new_val) + .saturating_add(one_minus_alpha.saturating_mul(*old_val)); + } } } @@ -1271,7 +1287,7 @@ pub fn quantile(data: &[I32F32], quantile: f64) -> I32F32 { } // Calculate the position in the sorted array corresponding to the quantile. - let pos = quantile * (len - 1) as f64; + let pos = quantile * (len.saturating_sub(1)) as f64; // Determine the lower index by flooring the position. let low = pos.floor() as usize; @@ -1281,17 +1297,26 @@ pub fn quantile(data: &[I32F32], quantile: f64) -> I32F32 { // If the low and high indices are the same, return the value at that index. if low == high { - sorted_data[low] + sorted_data + .get(low) + .copied() + .unwrap_or_else(|| I32F32::from_num(0)) } else { // Otherwise, perform linear interpolation between the low and high values. - let low_value = sorted_data[low]; - let high_value = sorted_data[high]; + let low_value = sorted_data + .get(low) + .copied() + .unwrap_or_else(|| I32F32::from_num(0)); + let high_value = sorted_data + .get(high) + .copied() + .unwrap_or_else(|| I32F32::from_num(0)); // Calculate the weight for interpolation. let weight = I32F32::from_num(pos - low as f64); - // Return the interpolated value. - low_value + (high_value - low_value) * weight + // Return the interpolated value using saturating operations. + low_value.saturating_add((high_value.saturating_sub(low_value)).saturating_mul(weight)) } } diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index dee30f358..918343564 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -669,8 +669,9 @@ impl Pallet { pub fn get_alpha_values_32(netuid: u16) -> (I32F32, I32F32) { let (alpha_low, alpha_high): (u16, u16) = AlphaValues::::get(netuid); - let converted_low = I32F32::from_num(alpha_low) / I32F32::from_num(u16::MAX); - let converted_high = I32F32::from_num(alpha_high) / I32F32::from_num(u16::MAX); + let converted_low = I32F32::from_num(alpha_low).saturating_div(I32F32::from_num(u16::MAX)); + let converted_high = + I32F32::from_num(alpha_high).saturating_div(I32F32::from_num(u16::MAX)); (converted_low, converted_high) } diff --git a/pallets/subtensor/tests/math.rs b/pallets/subtensor/tests/math.rs index c2b1e7128..35b383f68 100644 --- a/pallets/subtensor/tests/math.rs +++ b/pallets/subtensor/tests/math.rs @@ -1,3 +1,8 @@ +#![allow( + clippy::arithmetic_side_effects, + clippy::unwrap_used, + clippy::indexing_slicing +)] use substrate_fixed::types::{I32F32, I64F64}; use pallet_subtensor::math::*; diff --git a/scripts/specs/local.json b/scripts/specs/local.json deleted file mode 100644 index ea97f78db..000000000 --- a/scripts/specs/local.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "name": "Bittensor", - "id": "bittensor", - "chainType": "Development", - "bootNodes": [], - "telemetryEndpoints": null, - "protocolId": null, - "properties": { - "ss58Format": 13116, - "tokenDecimals": 9, - "tokenSymbol": "TAO" - }, - "codeSubstitutes": {}, - "genesis": { - "raw": { - "top": { - "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da923a05cabf6d3bde7ca3ef0d11596b5611cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x0000000000000000010000000000000000943577000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000001000000000000000010a5d4e80000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000001000000000000000010a5d4e80000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x00000000000000000100000000000000000064a7b3b6e00d0000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e5e802737cce3a54b0bc9e3d3e6be26e306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x0000000000000000010000000000000000943577000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9edeaa42c2163f68084a988529a0e2ec5e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x0000000000000000010000000000000000943577000000000000000000000000000000000000000000000000000000000000000000000080", - "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0xe502386e6f64652d73756274656e736f72", - "0x3a3488932ba83145d9efdd3fcf226dc44e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x3a3488932ba83145d9efdd3fcf226dc4ba7fb8745735dc3be2a2c61a72c39e78": "0x0c8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", - "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058bc88053e26875c16521070589474f090afa963724b5f53bf3986a96e9c501d422803497a408757b1ff0ab6df6775e02f35c8cc209a41e4f95287499a795d2d895eea95eb900b127b11ed177df10ffc4f5a23843442c8267bcb2d037318cf145d15f2db45e1b7c334ea451df98c665147fea2a3ace519a341b68e1fd12d6254c18c6e21c413526aee8428359f3b215ec7abebc115cb38b83c626f0f267ceddbe8baae4ba38f3af2236a8a3ad2d4f259d783cf1ab7dd7531d753906f1583e6cfcffaad6ad05c87def3abde2ec8a8b97e43f4ac17fd56db88d3a8932dae1765d7461db7c86de7e301a36bd589f6d121b029ad6e715d760e702201a95145451df99da823bf7543e2affa0d818306dea6b23b91809476ed1faae5475d0fbe4fd4e8a698c24ec8a80be2d25e33dd62763ee0328ca40f024d8228fd6dabe589609eef0720ccf3ed150b30cfa8e583e01e9892f7619e6d8d208a9607827960cb6fccc1e5b1bd87dd0dfd1575fc8179e2e5374756a0c5d27b43515c49cebed8b688b72d1154e9abb705858d5e972a50ca9ad5cd890c93d87ed86f69834078db2d60d552d615844004410145aa7411e3b7dde5f6dc05b16aa0ad4245bdfff870bd16942abd2d2858f4576af91d7a5b4ea4a1e59779e6b54d1a61ec8258f532ea93cd9f34ae9ae16d705cb1c5aec7c5f13ebc41ce064715d7a9ec7ec08c794e3e60cfcf9a35fc45616f8e6cc1f603046208298e2a205c1fab83c7b5ecad341f8358359fe3e535cf679e930f7e7c9c8797ccf35d078af319f3708fc9b50ffbf00a4258655b20bcad044506b182356394bcfc56e2e1c5f3858a5a2e6c599751f019d896715ce1c9f8b32df36ccb892bbd19f3c4ef7ee9d6db6aa28d25a3e5e75692cc33bf5fa2f7db2e0fd2b76dc59d44777570f5db2ec43dbfd58f7b3b3c47bfa2e660933e2645bd7d74f95d1eda49ab8354bfedf1dd35baaba3ab1ff7b80da910b665cb9635f4e81cfd563dbf9d0fd8a3fbf00634aa4d60cf07e19ee74e883cd76fd5fc6d483fd8f0ac9ee77e6cdfd501ef137d78a342704b67e7e8a9fb815dab1f6ced23fac11e7d9bf4833ddf3d7643e4e77d785b0885b7ad26d268f9f96d37e6b6fbd17dab9fec2d7293ac492755d805913dbf6ddb91eab76ad239da246bee1bfd606fdfe5c1d58f7b108fded5b17dbb4fdce846e3b72355a1d8b0eb31dfd16f3e7e3b21f273c36085a0b7d232cff6bd649ed10fb8a5b9baabe3dbaa10dcd2dbaf6f07647babbb39349f38a2db1a859f743ba390b53aae63f4e3c3c92b0d5e5fd81e7b9828fd5fec4ccc7c5e079f899a931e80782644006f3a0fdfd8c3444d55bd3cda9c091bbe7d9fc51e267698a079cdf7a6d8c3c44c55cdbc547a8dcb4bf43357cd54954bce6bae9a39f7d29988559565f7aaca3b16af8a55955511c0abb2e3b8cbbbefbdd8c3c4a8aa4667fe565355f5d80dcfe18cbde62a7e4d555dd7bdaa1addbbaae6db9990b7a9aa9898bf5455bc5655dae97615fd5655315eb5fd7418cf3b5755d7f55155c5aac2f9e85ebc771577afaa2e98abaeeb1c87abbce35055313197a92a9963d87154550c8ecb6032c7e12aec3854150e2de2b84acba92240c15415cc65647e829191a9bf61343a0e9cd1681b6954a666c6257a186ba399986d3257895e1c67352e25ce28e6456f84c96c1737533393d192cb35d2b4b879988c0be7cd685c8d0bc6198f4af49289974c4dcc72d0c1dbb844b7efe082c328871c606ab6a8e9e85cc5d7d1d16666ea515e7614a7830e578dae830e14c34aafd7e146d7b950a8abe85139a0b2928cf77a1d6a5caec377b86ae63be8e85cb55d07079def709576e590c3465f8fba648ee21c72c84187abb0eb505539e4e012bf030e38e05cc51d477bbd0e8ee3dc30b3c375aeaab90e0a0580e35055a8df80c375ae1afd86abbc9a731818ae797d0e3838bf01078e9f8e73c371ae72394ef61c9ee3aad273c09d9bef7055e93bc09cdfb0c3735c35f31baecaae83bab9a9bfd1f9cd55f437f0aaed37c7e174fa0d38aa0aa7aa707ec34f387eba8afb095e751d87a3aef28e8200c8e12aec395415007278ad2a001c078ea3ae8aaf5769cf51553ae8f09caa3a55d5e93030bfa92a1b9b9faaeae630303f5d25f35355d99c6e7315577d56073ee73bdcc0bd1ca7aa6caacae638e7b8d7ab46af55f572c3eb555b5db23af02fdfe1aa514d6275e077a82a1daa08a0ce761caa6a347a4c55c57cc3e1db555a2562abaad1f69baa1a9dd3ce5d45eb10ab037f73954c05b23af0a8aaca41e600a8aa5a55f538d0e754d50d7fadaaddc2133390b1e50561400194145d821351987106375ac0c412524cd0fbd4d46c75e06955bd7ea6eeeac0db5037eaec71e0d4fca6aa4ea6db54d52a083be082952b34c8a28c20a46a562e0841144e40042898e14a0c523557b954d3eac0d75495a96ed4e1a17ea8deab4ad55b1d789a9a459dfdcc55b11eb13af0335525534500534cb83ca6aa4a87a922d8493111ff52552ea497aa6a75c514354cb9e20d3174e10ba9b852d0b2832a4ee082378860861413f1a5abbcba6375e04b5545aa1b750250bf1dbd5761756775e0bb6a8a3a7b155723ab03cf55d55645804a31313faaaa1f7ce085177020862668b1414aab22788a091bc020030fbe88421367e0428a093e76555651ab038f55d5ac1b7574d46fa7f7aaab7e75e065f5a2ce7ebf8a453aac630b13696c8b016f5b4c90d1db28e6f950cde7e7e0df700f4cf13d329a1fc33c2ca40585d5b1edc31cf3ecf99bc370064fa25cc03bf3a098e7931ef3c4e730cfa823a7e21264743cc73c71d45f8e8e878fdf41c2313233f3e1e54dcc13296cfe4d8c125e1e5e5622d8b12ea3b67eb05908db328fbcfecdefb78bf227cd7a93cc035b328ffc7e835d0fc91be4aef9ebf2b1eb21f9175d4645d8dcf1b79b35dbae87dc0e480f781f9e54f645b72585bd8a105a296dcef78e97ccc3df2ff3c0ef99728c8ade61d7632bc7a88c51f13eacc3fb623a1e3edec43cf2f15b0cf3c8df30cf7549b7b7bbeec33eccb4890dcd6495357cd6b09e84f0e1f9b012c1beaaa4b0b70b02cff5e3c3c70a74f8c5b617f0b6b5c413ed31cfb6944843ef63b86753db0a4194de73cc037bbf395b81de7bc30ac21fb6b5c1dbc3af76b329c146efb7dbb6b778183f08e3613c105cf821f98f6bfe2629ec48b95bcc65ccf3c1fe2e8f00783f9651fb58b38d394963472a9bd3d242583779216d9193943bd2d8ddf21882ac164a403799dcf2188258ba09d7f0fced864816ec8278ac86e7b60b02590d1f212f61dbce98e7b3c17185c787ffe225a3f65ba4393c7efc7ebb217ca0583346ed571002b16dcee72da3beacf95fd6fbe59e651dfe7e5b1dbc78d8dbeee06d0b9ff0b6a5c419bd2dd619fd959a4f80de164b4a73d723080f7e5b2c28cd5fe689e74be6c1cecf98679bbf71d703ab0b5d84b4ac306a3bde4648ebcaead8de2017e93646616fb08bdf222582423d24a3e483781d0f3b20dbf1dbf5e0b66dbbab7eabbe8ed12f6bec917ef1cb28ec3e11a39fec4f9e28f6fc763daefbf0b66df763d64ff67c768c7e6fec917edff7c52fa3be231afb32ea5bd2d897515f128d7d1985dd274eba1de9764685541d8fd1efa6e537c84dba1de9f6764364c7fbb00f5f542876bca4cb28f8ed7ac42a1905efc33eecc33eecc33ebc395c81bcde1b56188c26d81673e118656214fcd5db62b13a8651f05bb36dbb2e425a5518b52d19b5dfaec7326aefc37bb32d6f2b000f7ebf1a8e12c025844082110e14e08802870870a400871338a0e05802c70c3864c07903a70c385cc0d9028e1670b08073059c24e060c1b1024e1570c680b3064e151c27e064c10606384bc0f1028e159c2c386ee0888143059c269c9a3831713ac109cac989930c4e524e313895e0f4c48904a7119c447082c1e905272a4e2e38b5e0c482d314a7159c547092e214c52905a727a7254e2138297162d96cb151834d1a6ccc607386d315365eb0d1820d176cdcb0d982cd136cb0d854c1e68a8d136ca26093868d146cd6b059824d19366ad8986193051b25d850c1660a3656b041c32608363ab0d9810d0e6ca8b079818d0b6c58356bb069814d086c96a8d962b3021b12d84c61a3840d133560a83143cd1b3561a841434d1a3650730609d48ca188484d156ab25063851a2c3543a869d508a1e68b1a1bd46451d3821a1798ac98ae608282090d53124c5e3031c1d485890d9318262a98d63055c1340453164c563049c194c5d432f1c01486a98a090b262198da3035c1f405931726304c6698a6605282290ca62098be309d61c262d282e90826374c5c98d4309161f281e98a498b290d13184c5c3039c10404d30f4c5d303dc1840493184c513019c1b40553114c4b3011c1d4035319a63168d2a0a1028d16342ba0b982a605335e98b9028d149a1cd0744163050d0f66ac40b3054d09686c40b3031a24d04ca181427383992ad0c860860a33659871c2cc1666c4307385c6053364a0918286043431a011028d17343f987183a607335866b4d01461260d334c98e102cd1068664013064d0a68b0a01963c68c192b336ccc2c81860a9a17d0d060a60d1a15d0644103049a20cc4c814604344dccac8186cacc1968aad0b4686040a304cd14344ecc9441c3021a14d01861a60b345fccbc4173821934cc8461660c334f984163260b33509891024d08689ea039c24c1a34ac992c345168b898d93213859926cc60810607336acca881e6c9cc1a334aa0f1014d143464cc9861060c3449a051020d143435a0118346073455d02c31f3051a30684c403382192dcc9c41c3040d116478b025610b43c604316ec43821e6093153882143e6083150648ce04203ee01fb00660c1826c87401630566093044803103060d9826c09c0173849a19d8cc80eb01f703a8051c4d402fc44001ab00ad50c241490c207849c30b1a3228c05491e1829bc0aa88b112b38492176294f082c5cb094869209da1f4059219bc30629af092821714bc3cf112c58b142f50d490e1c5043165d838f1122586093166bc94e0a5063065d494c1c58a12149e0c6a6210b3440d0c3a2b60b6c4b0202604312c1733b880c1458b4d134060c8e022034f8a182c782bb019a34406af0b253394c6502a4389557283f446e90def899a1dbc54f1b6a079824d161e0d5ec2f0a680298377050c1a3c1bc0bc518a527aa2c485578312095ec628f1c07b014c1a4a52785460d4504a41e904363f2895c0b4054c124a4f4a4d9496d8c650c16c6143020908a413904850a383ee065e153066e8aa2865d1b6301048545c66c07181a9c1512141814d81c301468517277037d0d6c06d81930657a5548512164a26e8b4285da1c4860783981294a2f0b2f0b428a1a0f403cf053053629ee09ac0256153c256041c36bc13acb421646e645b2881215b8354864c0a591a30647847801103460925253236322a90d0500a43a64686259b42c90aa535381f7831d88c6033041c4a9442807901eb0276059c28604dc06830d7805d8159814df1b47846f0bef0b6e011c1f382e703d296911224354c30785cd8c4701943cc19323e90f9c1b482e7468d0f4a5a486ba8e1c15cc326092e64d88ee072060f0cafe5b5e115618a3193b05521a1802482e9038f059e149a354a6dc820e1a509303488e102cc1a4a546aba501282cd19315b7849c20b19385a4a585eca78b11293859217de14ce4acc9398263c2b20137098c02fc001438c12355a783142290b1e162e53b028c026d464896981690c314cb85cf182048f8a17335caa88c912a305ae829d6028fc04cb8045c0266012f008b8043240601b70165c034e0b1c334a2af07250b305888612174a5db011e3e5082f4af06660d3454d1b36408819e3458c17267838b0f90293424d17385fc0d2f054c0d9c06b81cd183028d88801e60c2f4bf06e60d3841a2fe09c8173062e0b0c4b27a59b412783982e628010f3830c0899175910485890b220d5805b83bb02c706a90b2437485e88c122268b981abc54e1658d172bc88ca086063224c8c8c8969099912921b3929521434546073239c0a0c04e804581f94206063252646200d3021a2c302ec8be806102c6099826a019e01bb00c900c700bf00a500d38058805470d4805d8460d17e013201ad009d00a5c028e104026601311802b38620215e8800584508088241f922030050a4875017af04900020ee0830176f4f038d141e5500f001c72e8c4c1067b21d785c6f187c9910c145972e4e788224536188d48f253a4c848870e12942562a2a4882243458086947480e827091113241bf871000f9213cb84075213eb811f202382941451644889068afc001d2162027484e801012031b14c387064e8c892a19f251e38a2c80ea4274c70202dc184e82749069078400e29046b0349896572122ba7dbb23f4448828a30499244c910123f0cc0a15bc31aa186bda14bc3323102c9927680120d2409f2437464033f413f40e47468d8e9ce101474a488a0a0230a30c311441b48f213f4c6fefc68a08812a22244476c00c94f0774ba322c93202447887e8c28f2f3a3811c3a32ac120d244152c403433f48081541b2a48992fc70c0034786783786fd2922899224458e1441b2a47f8a48a28401444990505244118dd3896135a044c9d092224b901cc164e82749079414f1c00f500e5d18d6889f241b40c2240912458a50d2040912423f458886942cf9192a42848403194082c98e0e0c6b049222443f4c869028a2a6fbc206961891e4c8d00f1c9d16239414f163840d9d1bcbc4033f40459830f1c08f90244a903061f2f300da796199fc20214232a4e40822248ec0745dd8244a90101d1942c28822444a82981441b2a48f0c21d1325d1bdb81224a8a2059d2433f40434a249004c991238ad0800686d08e0bfb4374c4082441479a14f941e20789061031e267031b384264876e0b460421412203a74e0bbb01241a48a2a488224441481cd1c08f11457e888efc0c15c10409939f243f3f72ba2ccb04a8889f238ac8c232214af233f473441224efb0900489064edd15b608244c922019224ae2a7880782962c395264e8c788202448a4808a501294bae9d8582636dd1a7ba4c84d6785e540912344478c3842f423844b57850f20093a828408491224321d96052a424950068afc102151c44f123f2f9d1a3ba44401dcc29fa00c1c214a8284068a1015f13384c48824488a14a9e1a13302a6658c161bbdab6d5b2c56c355ac9993caa40297404c6230a552a98495309812866158092b956060606076ce2040e8b6c5ed65ee9c1c5962324a79b1bcba18e7956d316e51cbe0ce28774a97b81b6336e56e37e7cc7637f26e8c9217008bcd9d92356c6210ce39350cc3389331d664cb2b4d31c61f32f381ca76cee538e39c119331f25ce695bc0bb39d1c71e4d023cbba0c882c46c6622c31472925af9cab802cd565705787d49004a5ecd827010820d1d0cc90667677639c3146390ff0c3eefa1019eecec9cdb83b772f939c51ee2ebce694978c91658cb1db171e06d88d7025d6755df7d2c1d9611b9cd89c5376318b5266999499cc648ca4186324c5184d31c6518c3c33293bb91943082796cd441a1a1a9a19b189c519b3cd65dbb66d6e51ca99eddcddc81b25c718e78c514a1db9cb71e746192394122e94328b1ce51c41dec15b1357cabde9b96179ad84bb2b392e4bb91b6316e3dcb82b7751ebe2e222a38be43828a184127292831242c849c9711c849ce438c84108252739082117619451c61833b92be5ce29574239678c734a09e7ae8472e5aee7799e27a369ab693232329accca48b92b577630cef932e74a29658471f734a79438187bc17131f30c6986a70072772594504ab972ee686584a7488af565f6106d660e53464a19b75b2909e062c3739266e09430730024c852ae941287393d00e0b09225dc3877a38c526e8453ce95bb536e9453ee5c4c4a09e59472bb957399639492172e9c2b61e44ab952cad5e6dc6c7737c628a56429258c31d6ecca2861e48d51ca1823cb9d02c878c64cc6dd9d13de9065d9ce9c32e399935303e3c4b09789edc4260f01a8c9628c7277ce3927366fd0814deca56233eea491afc8dc71649edb99648c92e32527b6736272ee4a1c25c001458459f6c090120efc706072000951122486aaa701222470bc247e8e28429404c9d09222453041c281a123444c7238931ace06a8882319080a5232a4438e233f4336383f40458892f84982036705f0b3819a1826444c9024f17373ad009228414254c41126498e0c11152142c2010e2841edb003480289067e8696546fe8c84f5011a7af0092fc2c5972a4c8cf068e0c29216272a30391122543458cf819fa31624849119e020ce007c910910e0318c261b40330e28709939fa1251c50524491a19f264a8af8192a7284891145fc141100104f0106b0a4881f263d3678400348867e9adc60437464e827c9cf068a1840000528c0008cf829a284688907868ac0410bfa69f213542489231cb0e166034a828a6c2009920d6c4049500de4008efdf919d24091212544478a50121474e4a6071e9c2494748008c7912398e0003a5244113f403c051840cc0aa048122548980cfd3429b26300022832801fa023411928b28124488e6072e488224449fcbc49929f786d09bc824572b29122b8abb92a5a151515ad56731ad9d56ab51959c1a2aba868b55a652bad48b58aaa929122b82a2a8a468a201b298245456c648bb0a29554ad32232bb882bb5a1545235b54c446567055545454b4e21566648b8a562b69645745abd51a59c12268645745452bc946b6081ad9a2a2a2a2d565648ba69122b8824656b0680577751959c1155cadd8c8ae915dad381ad9d56ab592465670058dec0a1a59803402dc5dc801264736382a57e2baed74facde9dc10ee79ae2f97d3e5a9bbe91da493d6d00fb6cdb71ba2d3dd6d684d8d89865e5330eafa0c8d6260940bfd54fd423f95473f15897eaa86d714cc03e31b7d1d4631f0197d8dea29483cd03c7cfc7640e4b5609eeed7b7aeebceddefc390920e4fbdad28b290851559705183306ac0450dacd8380fca53e7fed1abea1c07b5609eedeb5d771fd6b493ae9df4e905f3907e7d239148a46d34e24a1c776f4487828c46dbb38f46a38d8374248480267d7b442a5121d824523744a749f524a43b7c57a1168cbafed324fe7a5a2beba7343ba4191c9d6e319f4178753d66ad228ad66958a7178cd27a54bf0ef4e8db320258dfb6a22f89863f6de2292745f43f2ba6c1852858a9d7274d3cf567f593a79b9a9fff2e2a32ae7e50fb88c66ff44ba2b56f2704aab520a3118d9f5e7c17157d5da36c8551d7c7f505a9f4f5e39dad402a17e4a2af98beeefddbd7a11647c07fc98e08fd1f3b22bfaafe957eb64e8cc201164f46525ada7c9f411fb0c7db4c1143c7dbd08ffb7492cd7df8f49dfee04eeff4762701340ba0b7b3b90fbbc09bdf69d80db1f9c7dd413640efeac856ec63d255437a3a897e36e7936e830d36d8106db0e15be46cb0e13eec793870e0f806398fe2c071ee7ae02091388ee3a6b07173c8e1946c7a5b5150e9cd86abc9fe23abab8694f4493ff8eea4d80637af275a964adc4b5d0fcea3dbdb89831dfc4dc780ab63c0f509e47a294bc3e8e634bf39cdcdcdcdcde8e638383737f7e1dfdcdc6437f524c48673b781e3b652095a61433fd8a76338ce1d47e53076797c3b7df3ef74fed6f9883fc1be79568a6d94388ee3388a73754374688e739ae37c8c3361b8d215a58483c3e1e0e0d0d208e7b11ba2d338f524c4f4d24d2f7dbba143b26f5e9231dce4822b954aa51225f5d44a2fd593909a933612e57e43b386529887bbfcc632384c568d5d91ef286c12d7dc360e1582cdbde384c09ebfa1dbb36edc4fd4e63ed1866ed750ec0c46c99be8a4c228791a7a3d815df954a32b8a105473f52464e6a4cfd4ec0d46c993a40cfdf1627fd89596cfdec0aec4d01f8f1b3b8379e0e40286fe78b02715e6217d579496df6216e67171198dfe71151fa81e65619ed1253357199dbb1ea37318cc03e907dbf33cefdbf9b0b7c50f5ad1de63d7c37319a2d35ee53018250fa56c9a06b54b8d1ed2b9108a6dca1953acc0828ceee96d6111450da2689ede1616694c89e28da641161a54691a74410316f4f6b26ddc7659fd549d1dd2cf6b784aa13c45b90cca5355455f344a2d1720cbbe6d1ca59b82543e831bc769d7e74ff0dbab3e09c93eff238b6d4c583f55c3af1a46226ef8ac1ba2b39df4eda47a3dc128b95d7ecb3a1fa8dee03cec0e3ff85989b8491ce9a5f98e0e05e99e3dc8cbbb2ff364f51b0a92dde541bc67efeaf797ba8ceade2df3b8d44f0876f7ac7e1ee9e32a1ddd2e51210490b6b75321d8db37c86ddbbb213abdd59390d1b58f6accc22879ed232a9847bbfcb234b4bc46a11446c96f3644de8988883c58b1afcd8f07947dfb8b6d70159b1f0f5e661562b34e81f2948ab990a20419b05245320b182d2042ca890754ab54ce9dd84935b193da02fe7a8279621bcc039b8adefea296e62a2dcf64dc10b3dcc0551801377cd08a968758540ea3422b1801f5124ed1db92794ef353a03c45bfa7598978f4dc1001d4047980bfa8cde680bf3a2188d4d3c78ec8e9b96bc0a66ea85ed481875d0336556b1675e0b76bc0a668dda8033f9282c33c9c51da6f361af04ebd2d2bb0e8fd927a5b5568e96561676c4b86711b0d78dcdbaac28b16f29ab133fd2494a7300ec3aa0a7b118645546f3032aae3c654d5f24c330ae5a98ccba03c2511905555f622eccb05e0f37da24c43cb7d3624365f56a158806f5b3368a389b8a114e6d93844a7235721aca1164ea371e86dcd808bd6b60b6274adeec00064f7e126dee81a94c21bd5531066b13abe88c56aa0f86dad0721d7b5fac97413d941da4f40b6b5d1af266a1fde628ff8f882166c64b8cc33b082595e1153c1967d3435293d7b5e5be699734e8d726ff005cccd19b4a063cf6f92cbae51c9a8ec421ef6f9f81dc843f61c8cca8e6254f6ec1b2af6f4c03ad9b31e3a432181ea81515914de95fdeba1b36f702343493a0589ad3db6568b4614360bfab480556f6f82b51064f5f6ed788a13094871db9ac1143da2add5e5a13d7b167b48cfeec51e98ca6e8a3ddeb31ffa307af6cda33035a25f0794d0a3cf004aebe86dc9e04a5789dc887ea39fb677808ceec018ad5126b0a794eed1d37698e214899eb6da808cf426608af4b3584378df2e4ea3b261d76308efdbe4341a7b3b07780ed854e7d12576cb1bdc4b74099822d5d3a80a95b6f414283da4b82f515a43aa0998e2fe2463b1baba04b625c57d939c76041ede87392a0478d809e109111b7b76ad24cf25b8e8dadb92411419b0da86de96142c2dc54ad3de96142d7a2b51d91faaafe7dc1c4a207efce77d9ecf9581e6571aa1af05622138f632cfb66410467f31df0d57a078fef7beae73265861cf8a84d7445ec7431fb8af732744ec79d809b1d3bb3a386e51a52f343a4ed145d3e66f2521482f583f083b21f25c3f16a4d11ce949c8850610ff63b26af8eb42bb65cb96339a6913998632bd8cfab635451afde60b8d863456227e6f2c1f2910d75310f8ebfcaf9e84c0ab126ddb9ae24a137113d8f13e9176a4b49968ebfdea823c856de38f583fb89b236ecc45ba955672cf93d19f6c11bf69745bcc1b7391d3e862615d5d10b21b1237c5dbb5dc71012b283a1bb2b35a18e0d3d96327844b8d7d8317e92b8a023906e2d3d9b9fb91a4858a82709f0d8d71dc2d601583cebe454e763e56525ac84867df1892485f91949edfcdb161dcecb08c1b651a37a29bea82f067fde057d45274d1f323ea69f42c168bc54a05918f352ea3e667fd227743e4630345ac22616a2253cf781881e0e1253d09e17ba0eb31f91a831452a2bc0e21765df3f1d8755d92de74b0053edcc3d463d61b465d75a564260366cb23c2983dcec7f9f81dd98c8f713ec64f56c19c87f32779d8dc27f96d49218508e62f39ab64d46617852d292761db565f97cb637e7f2d0fd83d30ea7a94de61d4f5edbe9bbe1e3f0ec90ef37cdc0df78c7efd87e5f1d591fd7a0ee6b9be8379b45f3ad973b8278b619eefd4d7aff7c03cdb82224a5fe7b8278675b25f3f2d0fd897466f18953d966067f678d86524e898ec27ec84d56d49d1446f4b0a11740ca332adde302ac3e8b53a2685bdad8ba315db4e17781fa3865f8e1b79c570c8b60d8dbed5eb18ede6b74da3d8b7fbb006847dfb56e27a5b5284a0afd5317bbb1f94df518c6e74dfd1fd886e67ffb643fac1d6ea977dfbb1f5fbc6dc4647ad6d713fba10dcb77e5927f124044ab07a68dbaa10935577ff3a8006356c69266b058b2846d05dddeab7d7ee13b7dd2e6e296f935bcadfee47768e9d10ae7e49f4e8fb5f9d8f51efeaf8b66b4b81341fdab70aa9fa8bad7dbb20598f2a50f62f3be4ef7db827cd96ae36287fb02d6f4678fc5884770837eeb0672a04fb8651f02ab882bc638b525a6a8f15c7151e94d2d2488462f4ede248a38bcf8d78ce67d947a30cd5a38cc28f28f749b1f96d44af499751a36fd9d5f9e83e7aa4d939badd27665b4749f48344dc0c9ff5181dd26c449751b0e3b1cec78cdbdb6ecb41da5dd31eb56f9162300e4175acdff56d8a771f8ef4fab61d573f5573d1a3509ef2be8c00afaa8a4a5f2ec0e8aae502c47b8f97dd1054c31fd2ed1ce57b745325ba05f70dd2aed26c44b5cec71c7da3d97de228db66fac1c36e884e5f57855270b8c083527aa194de1b8a78c5f11cc1aa3122f9ad5f90abc247faa97aa849f65989e4e7b91b82eaaf28d22f89eb1915b261c875185f04a7b7158518fdad3a0a298deaebdbc5611f11b7901cfaba0fcbae47ac4db20a04bf3deb32ea23628c5e8f9d901c3ad6d21e4a619e4b5e8a0d0e177844dcfc206f286443c358bf555f9ff453750efdc579d14fd5df9e883b5ed2d80941552885517b0d19b13a62d9f8f14c87f0beb5d2f1fb214fb6cbf65a592c3e5bd66af94887f0cec493fd65ac968fcfa874d63a90216416f9331afbaf63d8f5cfebef17f0e6bfbdd2d877603c5fe619619e6f4763c7fe03f3d8d0d88de09e98c24e04f36ceb045c34760b304f0ca3b12ff3e0e86fc968ecdbed0df3f0b16f4cb134c03334f68d39a6dca637186e2409c12dbd1ef3c4638f611e790c5b2d5105db66c0938fdb8a92a5bfac2ff8ebd737e7aa40de8e2f8b72853f93e926e3b8675307e2a6e3736ecedf8ed7b13ce0f7b16759e7f37aff79bd645b9b35785c94289ae1f937d007d809e06d88273d56f3b7ed0fb2fadbf31356347c228a86ac8e4a74ac1f64758fa450644a3e52225e4a7ee92e8f539051c77f8b46c70fed74ac42a59e22ffed96ee0f78f0b061277421af63bd61317315b69d2e1ca324a3623608a96988b797dd357929751436b73c7697c4dd10c9daef395bbacc56c41c5af2ae8e797948b14ffa753ee0f7a3e581d50f662721d8e1b1baad0ef851c36b5b3ea492513ccf9ffbd19e2fbb21dbeae06f1dad0ec60e593de997357f5707b312bd39b4ac318c82b031c84388f2a4b7854a78db82420c0daf1d7e73bed7be7863aa7d119942cd88b7ad1450d1a3d6befdc83efa46bfd181b86fdf34fa6df41444b258dd5d76ecedce947b3bd8dbe4b65d1da55f674a7a897efccff3c127d50f36fbe07bf7891e3d09219d4faa7c8d9e848c9a3f6aae36ab23625eecd9543cd31846c52e9d44b757c7a7fdf3868c5abbcdf288bfba21a7d511af551b128942566ff703b2a6b8dc87bdd8e35225ebc4c752fdb29e07d2befdd3be41acbed8ae753fe0b76dbb0f6f9976d8f980d77c18eb8268df8048ab23beb43a22abe5ea880bf0244b89eefec98edf46dcb9aba45d1da52122ae492f8de8179b7b890e71279a3dfab8b977746874a2d9a46f27a448f37d9844856ce0be41aea3a3ada35ffbe0f7f03b5db0ea316ad6da57253126826d88277b5b5074618877f5b6a0c8b2abe306ecdb521634ec788a8ea1b0794b77ab03639a310a3bc4be3428c8938d61ef96470f3e869d5b1ed837076363db97be4114f4b6a448410bc96ee98f6d21b8a55b1d256e75940e2990477a77eedb81bcd1b567c70ee4cdd24be79607bff46e796c97ce34a70485edfab65d356e920f045816559265a3d9c992518faed5afa8b36fe7032868d4a39a6494d54ffecb982f25f3b133dd24cc18755d32ea62fe267b08d95673fd7c9abf69dc622ef2dbea6d418145ef178ce60ba39f9cf4bb6a0f5925a33883b28aedfae5314f16446ed77551795d36607e83bc9be4b6eba17d9ebb1e3e71d28b6ef292f21a0141f86ddb4ed68fa8e5e72ffa717d7d63a3b7c7e6de3e29ec53902d234a6fbf5af67641b02cbd55863c5cf7e18cb94925a3ae6f0b792369210f579434886b494f4222509b808ae64b48b735bad31f51d8b09b91bb68033cf818468d28768d6687741e764259dfac8e7d36abac19a39847562e524fb2b10591f493f492bf7c5866d87d5852d8d9e7b173d723a326efbb6813d89236817d5ddf9881a410f231a39266f4c30e2786fdbaccb2ec59fd309f783146b7255f2836b3073c3ee4484b1b1329f60d76407cb0431f7cec1826b32cfb7641960431d2f2753747e6e37af6390f248f7dcbe8275b3e0e09ad1616ab895e7a4828011d5787fc1011b73ae489660b25a07975c8c725dd10239ddd873fd832abbb3964e7e3baacd97d223c362bc7a88bca2159b30927153678f97835ec7ef9d830525b90f9f9c98dbedc47dcb58f4634866a9336810d6913d8737ee385cb9204eb57c7a4de97075682bd6dd763a78bc7a8d1779a3bec7ac0faad1ad20f36d0bc768c7e75ecf47ea31ea3366b4cc8bc56e5561afd821d03b28faa89517baec62ca3761e6af55bb5768e42cad16f746d543fd9a36f8734f2079bfbb780d517bd9b83abdfe871a3425c8fe8361c223b0ac58ed0035e7c108f856463dba4df75f88c9e6015828dd51d7a835c467534466f18b59ff483f018bcceb0c16f0f28ff6e889cf57bcf675c3fa218d3f0da639867f478d368dbeab75dfb86d10d7bfcb651d8db328a3bec7ed4b46494f68d6e73942836f60df2c24882a365d4c7353cca94e3c66b58bff908e1a4db1acd12009f51a2ab618d6114cffa25d1f34bbfeceb76ba4846c17f1c6a31178e51398c8ac77e4661d5877de252493fd84270cb962d5bfa5bf57e52a298318ab7bb2a17c33cbb703b5ed2ed1be679c7e7581eb147f09a620bc2db9b18051fe36d9bf31dfab0612c2c81b7ad1440e951c36f116e92dbe50159fdc483f7e12d764292c5da328498b558b02eb234343646dc421f6096347acf70047d902c6eb43c788ce4f75f26eb0cd4898f5fe803d7853c7c597f59eff2f049410b56077fd7079f144cb13c6c73fde0b36ec8d6b07e45dd847b0343b0e16b626cbc137793e1349138be6da1004a0b951a3ede86e5b16ae8f819e8c376e4fe663a1eeb7a2cf1e0313eb6c9cec7a6814108611621940ce14491e08be00a32d6c5b6bced37e783519abf54080f689f437fbbbd2d1248e9a5db3f53e4578b85c7afac577dd2848ed4752830a9279827a6f936385ce07d41bddfe92fa6bfbdc2104f2e0a9f6014ff87f7c1279affed159aaf92f7a2ce18a8f0410c7c40050b8b95ba1e7fd14d45ca923630411521988110524f9ad0918a870293924f9ae0d43c1404a4e22f0a050129d51644e25543441d7eb4d2fcebaa2d88cc5f55e5137598c8f558558f3afcabee7cdb6ac57f6b65c9e04412a674210a595481096f5e4941861060000330aee8200ca94dc97f3b7d162b159f609e1c9a2f69a41b75beac1746812cb730c1137dd3db2a81179ac7d0ce6a2bf1637c8cbf8a5a1e5e6c74ac57516335c9ac404135c9e73f593f55c78bcecb215b46949e754af6ebf29ba4df040a5a367afe4ab26544e9eb1fb79c35c9b2d15792033c69f555a73829e2010d22f0800611ea9744c743badf8e023ca041848fd4f2179de2a468d948695fa1b8654b4f61e194e08a28a4300e5270508525972c1b3d85c5031a4460cd033c6965ac2d23ca2804cb464aabac6523b5ffb896dff56197876f5b4fd0a089604f61cd5021074b6881c54a6d75529455d65ed62f1ed66f4947222e55a0020dd6f084c54aed858069be0f17f1eae289c5b639df4e47b8b107fbe73573270a689f27921dcb7e55229a56bda813b9c71091f9acaa36eac4cfea804d655127de87633764d4f39fd77153d8e3b9ebc1a68e11a3aa2d88609f55b5c575ac3eeac46fd49129ecf3df3b8e1aa39b9a7453927ea87eb37cf337b89037b8457215396eb16dceb7585a727079c0de430a7b630ed2ed2cf668df6f5bbcaeebda62e7e30159f6075c1c5c1edc00271290caaedd87b7dc93914a9d277b60e69561555e286c0b300f104062cf0e113baa8caa4f167b8c609db8558805a7b9ece05ee8f616bb1e1ac7d5a09cdece9d909b2ebeb046900b358251f0237a84b70304f3e4d8893d269f1da8ad64ea083b07f3c4dc147be251d1ebe8f606438f609d78498d6054844f3ac66fdb8050e0134b8892b82a4b3c6419c13cdb23f7d8c5d80979b1220928e8ed44300a9e08a3e0b53a04a32090202c6084e6448fe71bcc7dff1cd4f2187d637a7258475ed6a4e1c17c0382513e3553f0be25a3e56b9810b3cdf331f5c7365f5ef5847966b5c02114e639719d32f3f591cf329ea9cb3a30f533c54bad3bd70b2646867479195aa38657ba7c0c0d6214cc254f9efc179e7c17be0793bd5017faa17adea3df4ecf3a244bf4cb48f47b77dbb3c3ae075789646fdc56cf287eceeae06bd4446f18c5c7688d1a1e5f1e4a91ff725a3e09e6919727629e2f68df388279e6e5839630cff640f94cfe50ca0bcf286cdf5e4a2965166b1980f43ed26f47c7c31b78f0f13e7169d08ede9531c6ea7131c6b8cc66ccdd6b7fed81ae8d7377f73c032cce7f31c638b1184fc61cec5f84579cb3fe86517bd1efa6d7d4c3ead89d1c13ce126c9b236bb6d7eef53d045ac9284914fbba8cf927e5650476e1c14ba2ab2ff9ebbabca0fc75280f744108ff65d90da3d674c1d84510fbe00814a9d8b68d444b8e3082511042f8edf4114144868010420889105187c05a9e9b600dc487958b5ca0f14407a0b7f584347a2bc16cf917fd8e60788cfac48bcad8d3695dfdb2beb4fa797d7dab5fd6739d4607d29e7da43dbb56b3ca28ac7e47f4f5597788fef641a621f05b354729acde4a300b0f3a414b6fcc6dd913a2f4ccc764f565ddbda379cdb94be6913907e4f297cd8592ae510fde054bbb5cc68a135aed79b16770d17172db0521dd6b62436ff73ebb212f7731b5a669307501abde5cbc2fa3bc7b77b9e67212fd4adf2f80e6302ed4e5db310036cd7dd8e5daec8420fd8876f9ec84d0fe65a57a5ac0aa4bf760eefd85c9aa612a123674a99eb603c1bcf40de6a5c3c0bc54fa320aa67e47a04af5e52ef7892ef42b7dbafcea80c0a6f90bfd5e4eaa38aef05eea109abb54209ac3542099120d32b5cb3dfabdd42053d354adee632704dee5dc0591a992515dfd7597fad5d4651477c928ce13c2ab1f6cd363be9d10a6c7dc44bfa28e71a130e6b013e27bb98b912e7d7642c4d8b013e2f3fe72971adb543f271df3ebb3f3c11d734d08ef2fb554bfeb44b163fe494669da4cfd561d53677e9d7be61a9d7171a92ec7718547145d3ea244b03597c32cbc938fedd9610edbab40db5dbcc37bf51bf5f60e687b366a98ec09643c414a6fb013627b96d16f3bcc37184a82cf9c40a5b36fdf24074365be6d0753bf55c3bc443f5397fe42bf23fae5cb3ca47f31ed7d9987e62617977ba6fa79ed3de634741915533faf5d4ea22e5feeb1794c4cfdb276a9dfcc5f4eba0d5dd621d52f6baf7edc4bff0b9d9fa15f90a965ce1d8636812d73d905e196390c850d3b213088ccb3fa6d97b90c25c9c8c0c06e012b181919cacd9d0f98fac18639e9251a738fbedc857ea676792c1dc8fbcb4bde5feeb9bcf498ea729f0843b95de8c71d6bd26128f7e9e5402ef7fe5271d8c03b0571b97797daddabdd9751dd4ba40ab49dab41a6de6af6bd4f1c51afb5af87ef88dec7aec7055941a66e021b5ea6699814861646b463c7768c6ef5dbc16d0fe99f69ebe9be8725cad18e6eb4468d1e98673b7f47167b4875a30e867d5efc4c1162ff50fd65d8e156738c60cec61205dbdcfaad7a7fe58c2563ab3496f5e87c0061cf8e51f90dabdf6669eca4977f5c6f4cafe9bb91f9f7997f39ddfd43f5c6fccbd1bb53fad7436751a79bd9893da89dc63edad13bdb3faf395aa386a71d4810cc23d3fdcb7af48d3da6a8c39c0c8da13e8cc20e43816014f6174a8451d85d28118cc2ee510b300a7b891a591dd8b3d8038475b0b395c64eaa5b676ac63eaf475be50bda818259e5b2a552064e2cb66debe7f572f1d7bf79396bb6f0e0e7f940b3663df0f81f17ebf5794558421afdde96952e9a354dd334f929df75582b4acbde56078f5b2d7b935cc63d30df8e61d825e4829bf5933d60fd5618d6f5c07ca2ec7a4086c226985134ecbb519807bb6c3536876c0c8551f23e31c8d698c73cf025f92896d006199d436f8b09563413de60421abd45eeb2424a8954f014b067e52818859da1300ff6797ea272144661dc3f8ed2d7e7277bfee326f608f3593fd263fe75df7997e51e97cfc36c9f44b1b3f3bdd263e8b28ef711c5de3ebfb1e7a596ea47243bfb85511c5778a44a241bd34e715ce175952836a69deb0f2078d95805c224916cae0a8f2836fcfe238acd7deb07a95c2dcf26803e6415883b1684ab40f05763d756bf55ec865c0d2bf6ab7e1ca5279b00f230274a66a69ec7768e68235ac2357c39414cfd1d3193985a5620cc639ef81ea3c70e870dbceb535c1c00e52997c73c763e5e8efdcb3ad3c1ee429775b0c31ce699357ebbb82cf6b8fcd0383ae5e5179df27218eab1cef58d728cba2e3aff4297668c9acf611eac7ea69eda5fa8cb47141ea3538986d16354bc4673302a7e52179a459d98c588d54fd5d825ad17476913401ee4e531faad1abb2ea1d8d74562171a4008ab083275101d9c90c5131a9e6990a927bc941242256c8fdf20cbc949fac9ce2ac36fc792197216db7646c578a81c548e9c30624c9f6cc8515e5d0fc9a82fbc6fcf68f81f180577300a5e18866152320a9bd875de66fa03a3a00fa32084b2a272ae1c7973465dfb6de9ceca5b530f8cbabe8351d77d78eb4ed05a69e9310a4ee1cd4b468618e94ba24f409260b1584ef4bed45f122c160b4a2f1056138e6e706db0c5310ff62fe8ead9fe25d1d7affac96bdf340aadf07618c5cc28f631ca7a6014ff88d8b3316e96b85920dc417f8831477f3bdacb32cf83ff4cfdbd25f3354d3bc73ddb35ed7cc127acc33775564f42645f97ad8313b270425ff5747dd557fd60bfe5653fb04a6bd4f0becd82823f22f6f8441dd662baea67c2f26d961d8ce247fa03118ce27be1418d271956a6a685b76fc46fd925cd2446bf0d238cc60e77e05d509a4fc43cd98f582d2dd968995523182531aaed1b99c4a276850185134478a3af9971d91b7d552b298058a00dbc6dd2ebdba472ae95c67185b756fa83513a0c4f832ce6913d44ac333fda34f4fcb65a7acaba84513352ec1bfdb21ed50f3ee91e9816b4563aab4db0d60e3b212a2cec8005acd62ad0fc05330e6219c178022c72bc28a3a95d19bc94524a294787df8e96e72e2997d31048cf8ea8c3cf32cf337d3b1a1e16c1dbceff76b43c77fe4eece99e659eb7b167a3cef796ffded19da82373304a6abf3e207d3d763db411f582725ad5c517d6c042cb76f455e16a6389656b7235905002b66cd92249e7b8aeebba51fd56bda3b7ab887a5bd21bfd765a32aacb18d57124dae46aa0ebf27b1fceb8675bb0c562b19ea4e07bfbb67d7f8fbe5b3eaef4cff3fe997a30ecf005dec7715cd08e26bd3bff8644b7777419e5d1659dcfd4db4b74fb45b7c3ce47122c168bd55c5d466df51b3d3b46bf91e7433e9b7565e53a9639c8b06f5c43ef9ff44a939b27ddf47c37cf95267612fdbccfd45c1d925810d7596d7235d0fcbc966625c84db82e59c229de97d37c9de1b57376fecd25bc6ad08ea22e48a10756fad2328c7a49364b5f35837c826d75f0e0656fa5ecfb0c9bf1b27e3b5afe2f87f997d5a8e16d1fc1d01a353ceea3bf508e512e672e8b69f9cfebeea6e5bd9cee4a3db43c8975e4bbc3ae874b5d46c9ec1efd4cddbd443f547727d16fa7bb3a2439fa651bfdde5fd6d9a55db2b7fb212f9a644703c92a2b97c32f30de60dbb6de30cac42899a3258a5110ab1e37efc35cc70941c9a859bfa09cc6be8d508c92bfea477475f68955ed17cdb9e6b3a2be32988351b2ee304ab294702f52ceea90f7182525a324a364fd8276ac3a7855b1452919152517a0b765858a8e11c2039212f6a21169131a447e3d7d7d68a7af2a54ea28e532aaeb11bf92f01ac226bf69e84e0833da687eb4f244f3634972917eb047d72ebb20aecb4b49773a7e0447f11ac5d123772e7eabdfaa37d8f9183dc2d94520bb93ac3ac9b6c9c54e083f7ecb38d84520fbfc063b0a340153d99d34015373dec93c761f2622bb938c88ec9884918a2d8d8e57cdce0c323a3e5e1f7dbadce5932e91158d52d77779609fb568949a4340d17e5d14a634ba855002a00abcd19bc0bec576d628755dbef46d33bcd86892516fe76890516fa4cbce07fc0bf78f02056de7de7134c9f6ee2ff45bb50b7ca9748f0ac52d5b8280b504044fa2a7ad33e3491954942186cee96d9561a58cd6c56df444eabad961dc464f1b899eb6eeeab04e487733a23436eb125955ad52a3da047698ba3a3a050ac6d5ed3e7d55d5bc8e5d9bb5e8ba3eaf2aca3e2b94a7e6a13ca5d5a2ecba7e1d9b71bb7eda649197a9bc24ae7ada7e5d87b09eb6abaee2e175d58d3a59d1e4cc0acc2399874b2f2222e545a07b510cbf9cbb93ee2fdf8ec8cbb9aaaa7937bf1d11dd5f2807b3ef89503949e14084539ee49679b68938a1397727de69be1d119a73d5873b22bcd39091868e8764b47175446aee728e762751273527fdea2860f3ee9312a9b94b55911ef35d1edab78b7bce55249b77555573ee9b325127dc6d9e439dd8bce631d4898a7bcd35faad9a8baee96d9161a5b78b26190519f5f5986f1bec28407acc4bd409e9a5bb5015e92ef7a88a74efdb51a02686aa4acf3947378cbb541c05542eefae2addf4ed88d4dca6aabc772ed489cd9dd43ba1571539b1e12f8fb9cabbcb4b54f5ab6cea55f5f426ea44a5e2de5d257397aa325d3573986f2a873a51a9ba7baa9be338a7ae8e08e92ebf3a22bcd754d52a553a0d55c154a95e9408e9ba4b75e2dde51bc6c52e02dc6daa93eea463ddac446aee847be92f54d59d548b9cd03c8708ae2e6173eea55a344ab9bca345a394b7cbe3aadf4e4f81423a578b4629eef32eb4748f76f7891d85a91f30025e5571ef6a13a46f312b50d0e5721fdeeab7d3da4795359ae4fa844bd8dca5aa600a87ea4475ba0ae7aa9be378ce338a9da34bd83ce73454653acdb7590229bd6544992590d2530c2a7a97d0f088b72d1bbcd1f22d6f832c9840861562e831d818830b2584a18427ca784209678c61c51850c478438c2b625411438bde38eea227ac0ad17a1ef6a8b12cbd6c60597a2e1b3d0ff0a425adb005abe7b19ebd91b88b9eae63f474d5203daf75d6db05e1010d22b0b82ec8019eb47a961185f49caa8229d50db52b5d552b94a7bc5ad4d598ab6885f254a916d970eeaf453634012501292656a9d24b8fb90f438a8286c741a13ce5721c7a438bbac7fc548bbcc7785e2dea2a771baa5aa56a8412d040df421ebe6d7d11a51ba64c14ca53aa4c751594a78a484fb99c86aa666a1189f498ab646ad1cb632a94a7bac354284fb9d4a2970ae529d2b9ba44a9ab1b75be6d7d01a5612592bb848695035e19fd5d53a874d05ae9afa4b4d639807b772ff674154a77526da2ab4b242045dad873f9ac734077974321ddab4d90ea16f3dde5b12e8855cfab024160952a718a7459e4652ac86279496cab9eae93dec453a4cf7f4b7ad6d3558978f09f918d3d5d75d29dbb7c067d80f5b3a3c0591d11eea4aa229d23759cb45965ecadb16dcbada2cd8a0105ad284390c2d0041e67ea6bc78e0d7d3d877b3875dd248434fafacd0f7ddd8b812d106c7b010f9e9f31c75f2bcd9004190780efd0db128215bd241d2150f11ce6f978687898d3f0f028e64135bc897b606a5b5ebcd1f01ef3c086df1cb800121a5e900197f01807bd2d20bca03906bd2d1faca151bd2d1fdc2008d1125afa5b7931fb43e20447e87d092ee14522f4b676d046c3307a5b3ac0a273f4b6726005763f464ac0d24246465a60d04245a4fe88ac4005530cfa23c222069b15fa23228115aefe98408107bd4376008295fe987461a53763b1248bb55bfabd67293621d818ac3fb65f4c6063a30a043fda560ea8f4d0662db45bbafb45f93ecc9d8eab997e3fb6e77ea3dffc05857c978356771f7dbb46ab038e1ebb6f27a4fbe83edcf9c8ce1d52ee23da9d3b21da21ece8c715ae0e786e386a92b5505c03abf719fd8860f321ec1506790df051049ea46245cfc78c23c288dce20d39b2610822220ea68836d880ba3e04f37c3928ef88ebd756098279467dfd07eeb952dbbac195be72e86fcf00407f9b061b36f4b76df4f51cd751cc039979f2f2fcf28966e7308f10dcd2f0d7d09e680a29d2f073a950021a760bd81cbc5d1cf60da31f087a5ee5c4495156c4fca8d5ac7ef1fb6ffa9817326bac4e7ee6e1f80df2107816120f671502cf37310fc6337fc9cffda40b8fd16d6d61a527bdda639e9d41d40faaa1e71a36c77ebb2d9b633f599b23082bd1f01c578b46c5b630049ed76a69844acb6b50c01378dbda224acf6fac96efb690d2f25be9ddf731a69be7a098675e5e6a1ac67142aae6b6edadb4019d8464bf9e6599105b362265c7be69a36f6ba3ea03bb768d3b4661cf0f8e80ae6ba391d60d911f55a0eb9f3c936e88bcc61d685e3eabdf3c57b563abbe68f7115df5b66aae4427fdb6470ea8d35ea24f9e3c79527a13bb0280dcf330d5c4ae00bed8b3c2944fdcd446b7bf55cb1e386ce0758fb10bb27dd6affb564fdbb35fe72e48574f5b15824d3a7a866540981011c332ad7e1bfdb89fb4ef49ab3eae6fdf6a73db760d68f48f1bb2552d6ef59b1f9da31bd428dcbe36f66c8a833eb012bd272d8f9d1e76fcc03cdb457035e26d0c1b045629be93782662752201293ed72286367001200156005252213c20f91cfad3024ac3f3b50a32baae5f754ab66bf455a764d79766bf68047840038c2da9bdbca63829da32a2a4b2cada32a27caabe344973e80de6d04cd8b2671f7fb97eb23932ac1f7f85540d3308bf95e0856e94d8d24d300c72fd88b885601a7a5e7ecbe897bd89d74192a5447f7c212c0d4a6ce926d9774d5899721f0b654f42a0440f3599adbd09d64c56add565943c7f429896bece442c147bd65310d8ea78d6ee13b3489b70cb6fcc7073f0a790aae5b52fc2c34fc3646b873432d74ffb6a9a10aace2a10c4aa8f3d7f0a45d9b202c5c70ab4e7ba87cfe0d4c216cfdfa284bd041da4808c2c66280318a9ac7380132a3061250a1348a0021ba98d3afc95b206e17d28d49741d4622e5958699905e17d9e44e5601e1b9abfc33cfc1dcc13ad48895c7fa8e61fb8c55caecaf5b6b208a3655d5d90dd0043119a10831ab0a0075568426a15042ab26cc1066c74e1054748edb7d6400c1d3f9aadbe46356063c43d7643e2473538a3e3bb05601509af3fc96235d168c4c1ec1f6c20f85136e2d9f5108243e2619d5d0fa11ef0db378c83f31becaebd3b5bb9a076697c65d7c519d4344d3b731b85fd9a77dd298b283a7ef4d3bb2c9e747cec38ee5c47e2361b1f1b0e8e0729e1e028412a5dc775f7e11329eb4ed77e3a6930387c06cee9743a9d288e37713e731c2d9807e7d7b7181b4ef18d930d5c070364f3d1771aa6f36123247ef4f25533ce319cee34efbe75f4a405f39c7e1deb7a701c77731fc6e1e850100ec7b9d2b9ecadb1f12e843c648432c65acf6af6a05c78866c2be5304fbc940ce52a3ee3b2749497cb033e8bb1d69c0859890d7ee55972bc2afe92b588d5e04514562ac6c7ca58441d081d00610b3e56895dac5270c5c083289610a5acbed8b265cb16b8684c71c30b2cac2ab24ac44252811042082184104208218410420821849716584ca7ecd26e30e5f085cd63d8101825428960149402a79d8165628cb02fb6b903eeda62ca196c7108d30a36beb9c93444444163184684514460ffd6cae8d87d7888d833aa26d81886812103281ac3fefa05e5cc6e08db9a1aed61c794c326d3515a2f1852b4f4b78325e36e8aed14641fff636b5010aac4bd71d763887d648c928c62ee249c1b2389b763f743feea66fc015707fcfaf0115bd6587fc4e60abfb099a0e3b685c7ff3cb8bbeb13d963af790a2729cf877d9235569921c1fb638f0f5e28411665b8a20a5b8090826f9841c50b3278420b8c608514bc177bb68c2b5c904182161258408214bc29f6acaab8020d296b7082064b78420a06f11a14213688f89cb83b858e4ee818639c13c3306cce69a4b16f5b78df6a9914b2e6b3ec33f9c44974f5bc8a51f149109192e03dc6cb2ce12d611e78c984c7d7def0f87b1de8436c7e0e6a79ec82d16c80660134df9353443066f046c7e8e53014831a1ebcec05c30a2b54f0e04b31708207df8db18607ff52022878f0db0d1eb331edc325cf0a1efc4b2f18537ed02bc456a2c2db2a6e74d155dca09231cf5671038be67355bc40a5f931acd4fc6e27f97992d5476a56283b7827a2e453b3d794cab6fb317ba178a9d95be77d18a350b2d48c81053a1b78f01b94d3d79e82c053109935d657c35f5ceac283df7848dac283e7a8406a23e60a0ffe450b1ebc8c13591264b6f0e0b11178f0322ee0bc59ea2210b76c91bb654b6ac68e1148406ac290c0839f4f36283cd3b67c18e55278b919a3ae2adbc8c24695305a56304a422a58aa242c4004911d3dec48c9b1dc1ca82fa77757c732c7d823874499622e6f4e0cdbd1c3ce6814196a397260913188ed94d79c13caabeb61ba2649a720b2bfa2e6f3c62d86bd3979c96c75605ae97b0f09de5e9efcb617e421cb346db46d1cd775244d1b8db29d1c3fb8b94c761d9791e4247dab16825bfa14046b10343ff6a76aaea8d511d3c30ec38d268ee27d595b807d7ee861c7fb0da3e6cbcb255f5e5ee20bc3978d217dab66ae44308ae348a452c9f376f4e8c16567b7c70f6e0e66621dd7c578a520aee5ebc72d6f6a6666097f51a1a2e64b18e3795d0fce2b83f771edf20213d3f5e0646464e0a8d483d4fd809d6da9043dd2090bcc2bb42d6ffc8da901b6f476434a4d84e0aee18b2d0dbb21330deb09c8b9fe82c4fe4030d443c8c9181a7e0149b058ac281a3e625bb66c31c38fedad1484fb03013621933e27d88f2833267daa8edf4a3eb80cd3b81169efc3dd6a1cd7fde8b6ebbac841d8f518753e640b752d4f5a1289242fd2e7a4bbaec775ee7cec2f59afd501cf9123c3bd485f51cbd5017f741d2b95ab633946c528617463dbc72865a44c822857c7ce6063976f35f42376067980ff91c5306ac5105e017be5a9170c2cb080590ca33c66ea71f0372666c2fb627a3c8f210ee6fce67cb108f91d8fc11e386ce03d0a3d22f6f4eac42741cb123a7ea1d031d27ec3ce6775b4588cdafebe9fad28919b743717215fbcc1a8cdb2b8abe3834298d55805428160547c090c98319860c4e07ddbcad2970982f76d4b09ef3e400081d7a7a67d6ad514757e516679b27ea8a8e900c235f859b6a6e8226408593256c2e3c3ca2c0fde87dbe86090c1c4f4e025fb810be9061ea74689c405498ab1a2c3b8972c5cc6c940c9366d34e23231a4b671325a641c87890093694176913818164859e260c6b034831e0733020fae3702c2267b831ce535b14c1b6ddcd59bd69bc7413a7435d1e5955c482452c7b9701cb78d5cb40c9b735e9e77c9085d562736d64139fc86f5c4505aa6b70aeded6095241c789f8494c3e27d92e86a0f7a9099f7227dab96ab03421a4383972dc888c0fb320dd26c0cef9390cadcc0fb32483112785f06a90c15de9741fea8491d1149af26c52671cda24e6f256edb18d253904f7635f52683383a243bc834b1980cd376237dabce7a5b0fae0b49eb22b8823c85003d0c63715860418e107a8c8ae1c28b9274d6e16b5c962c46cbcd1b37591737d78d1637d7bcb9b9b999c2cdc4726eb27873a3851b2cbbf16206ac4c63dddcdcdcb0b2c0d246373737ac9b1b5613acd1e653e566e320961b9f2a375c77c3ba62a54a6bc70e8a05b1b02016964c161de9c60a1416a97403b1dcb02016160b58252f422c37f1e66605379e0b18e67c948f974b8c318c9b9b9b9b9b432cac9b9b9b1b16c4c2825858100b0b62614161bdccefac2b57b1b666c2b0c1de96797661cf429d6f26268387cb033b3c431fe421af8eeb92c64beb564686332dae9c15f6e0e5e1ba4625cd16753d8b3ddcea5c702646f272246173f79b84ab91767bbbad0b74d9c35f7cd4e1e9c30f9007fed26b51d727d51675fdb1a75b9debfc1fa00fbb3c64679bbefee96899befea19a3feeef87b9a86bd4a4232fea3acfbe3e302356b3d9269b979e51ef797b362e0feccbcb706124c90a57c775acb4dfa2841ad0b62e2db0345f4acbdea087615d083866d087f8edbd8c813cc4f0c083ff3c8ed4a61eced864329920668a2693a954c58bef7a4429bd43de64f2bcad18e818a4c265125c1d2ebd5db3b342b2c1c4b88edb6e3a27470f1db78d4ca61f85dad9e1d2c0b26df4bde99c91966599e799fadbb183b4854c1b9d61c54acf8669c454a1318fd6fb8f1a4a591d2f57186ddc324fd68b6559e6799087271b87711db7cc33ab5469ae2db03a322474a42b8c15c3b6b16cf09ed1ccdf86d171db8f42edececd82163035249b281054b4bd1500ba3b0264a5e6f1afded58665d6999293c6e998777b00e9fb31d3053b8bc7059ecc1683605e60b1efcf515753c7ce1d6a5b1e478ce0db7837598157bb00aa5441d3ef62f27fb77a3fd8be96f478ffe71fded186599e67999c9c4ac86526044e0c17f504af337186e99c7a5f79bcb8b572275dc36d2326c5e2e32bab00b8c216dddcad8eae29a156c58b0fe3c20e0e7d69fe75901c30657c0c8028bc7058661971abc512f185544e9cf8b5c785b0b66142fbbc20cdeb5456481a7e940dec0cb822065e065459828f0e4182c060f33233ae175bd6054f1840806ef650d19056feb0543065f5c5378a55e30aa18c3162f53c15cc2c36060064feb05c38a1ab41c82377bc1b0428cfebcbea8895131de0debc073ed8147c4105052f09ee9dc23536d50d133bd604831436fbd606051042c7308f37ce20c3bd7a37df439bfc33ddbe777700fd7d37d1e08f778acb39fef284737baacc31f518f7574f81926b7a0a56398676e6a6e81d573e6cc0ab45622b312db2bb765387540c5ce629b734e6ef2e7e79c5f49b3f96c7ede94e54acf989ab37e5abc6d490337dc884a70c38ddf00c009249880550531603082308ce082374690061694800acf0db2f880ea90e50a4d0b338b0b465890a205fb85547a1972c1e30ab9e02dacabde628c52a49c02c228175f95a11461af2cf2bc28101f4665494116b38c4023dd5c8bc974821cb041031608c10554f081a505114eb004264e10840264c18d2e0b69206931e990052990b2a085b9430b64957a5b5868a2bf9dc983eddb7f1f5f9fdfe69ca32bee883a3bc40e2ac6f8688a3afb49eda2ec451d39ba248d59d41962a30e8443449d3d5093d8533c805debed8810812c560a23b2ad145681441d48614b2f4abdad2b5cd92b2d1fb1982bfa3004267001085558430668f00004264c90821968010750f4908522c42c1c4126262607a328ae31bdad2b80a1b7ded615b2d05fcece649ec2b6391f53693ec482f765cd5dc41eed7cd8825534665d6128583605bb883afccdc8114b2016900a84b29d7cca48ec199def99b61185298c0e31faa83ad18e55544689a8a70860cf9e5527dbb1ba93dad406b9518f21326df40cfb28c3e8a67df44dfba8b23096127bb2f3198bd883b1165c802b58a20e0f01ad5c94b160143c6441285187874822eaf04f11c88e3a4500bb76ec5add893a3cc4a30eb314ca58441dfe9731958be709ae50a5b7c5c614ada3b77505163453591edeea909797585a9e5359d4c9e27d4ca5e5632bf638e1432bb187b15f5715cdf3b1ca2a9a67782c624f95d8e3e4fa95d87355d51611805ee08118ac5476ecaa22ac16cdba45bc93a2eb914aeca922257621bb883d100831d8a29592972d7925f65c6778438abcac127b3665a5e52516924a96d823a39696672a5822bf6125f6f850032b54ae4456e42cf10d169428534829dbdab183ba094b84d2f21f6b9158ccb065cb036e252c1a5e880897672762fee53da674df6e3bec186c61c7601790ca4b8556de6056576195acc22b5ab06439c208118cf2be4a6df25d4d420ac4422e812c8fc229209428434243117039f608cc77ffb13d25fb10112aeea3bb9c0855e9dbe7b74887e2bd5fdeaf21a1a1089c22a08dae7d53a40f0d41aa5ed481a7c02902a4bffca53a49a956a91f3c45d48177a12c25eac07bf4c49f5fa5b6481db0a927a42e02a36b8f446c277d3b2238224a95a1441d78523dc577f51c2502893af0233a44d481d7e829d621b26a8a3af058454d7ad113d72176441df855aa88b48a3cd3b02d87212767071832c8dab1630d1f2080300a0934d8411678d0852424c18d265481021637864005326841802c67d0b4b0448d9639cb90b245135fdc00095f3049c115428881139420411503c822052e4b1660b45c33bd2d3658fda19870b11163bc39a3c040e29b9be2ac3e1c635c030b1db98e395ff001228635aef467ba3832fa42427f3b47d8d9d9e19d951894876b5c8f31c60b075a8cc9cbaa4fd48157171b108c0212b3c7ac7a1c677c26808e59c0a263ccc20d3ac2548cf50bda7169815e0ed68911024b8ca00ad613c030811442ace10a2cc0280312baa88200d6a0d25e6f6b0d29fda12614b65d1e13c68b1bacd1b202e549961402a4b00233c080628c24a091da9b86206247f48940624f96da6f05b230051664d0dbeae0bdb3de4a34d183d90b7ea78687d7211bdee705e5f40f7507a3600f700c6bb8a0bf6df578d81a2b90f30bd3cb0165ad6ba8a0f932e37b3af46f726aaee0cdded61a51fa43491bccc3b845993dc20a149f5d3e51c2cb86e76264d6ea573fa8eaf98d7ed73120783902928732c633c3bbaa908635587fa367538c454bf36fb6c869bfeaa75d5102c5679f5d8f2827d601d99eb0b79bf7e120d767fdae8b0271dce3c12e7e3bb9b3c1a9464e19725b39391c977d413bb68c725009def5ed4f36cfac259f63ecf24d92ec68ac02c9cfcac5738c624601bb08f9024b73cc235d847c318574f1187592fc93ac3eb8253f915676b0682cf8989e275e142a30d1f03d300f0e0dbf8379b635852b0d79e0819091b90bc2bd315c267185573c2c36185d321f46fd205de4bfadd2fb13177d5db5a7597d70cf6f4b0b54b43c10ee892d168ba5446a9f4816117a6f0123dcf3a4d4da625b6a58a1f74704e9a0f74b98c7eb3d11f36c4b0d29bd4f827958dcc3a9fda4425a6950214338830a19c215466de7589e0f42d1cb4307a0bf1dbd876778dcdb52834a7f3fb4901616143fd629d80da332d6d963d545c8175d306a1ba6b795c6e62cc69107576f4b0a530cf132d92353fbf5b0c3ebda2cfbd04e6755a8d430aba720500a1acd1fda69ae4d64bc4f84cfbe658cbac8f07ecc671f7799d1cc0c6f3e9b07cabe8ce20f69d728ec3949f35aed31363c6b9f139e68f6fc78686a13321faecc32d8f59818f6f189e610a040850c0a519037f4b6a0e0032864616591e0a380ede981777b18230aded743c3df7c413bbc184641b86c4881051b58a10463f882151a208508ac34b8184317567248610499145020634c6278de4a8a2f9c40055e60010c400863870a2ec6a06207285043187678e35243b7430c2fcb11d010862bb0600a68b452f259ecd92c60a8800c2fbe78c10cacf0e8d05168a3150530b47c0ef7cc947c0eee91a91831bc6d8f7b20cbcb815aaeb7050518f487ba82151b4ca7edf1c9e60d373926d3cda12085c6373761a6d883559f18a130820d0820dbc22e7bb06aba2a149ecc1b93a9879daceb6d5d29437f3b0daf14f4ecea7c30bd87dbc3ad0efcbeb43d3b1f62470f6405c13ccbb38cfa68e008bc6d45218d76e96d41010b2d05efdb918739fd05e574bcbcdc6114ec8151f00a1a2de565fd7ad83165eec49e5939d6811ef7c894fc95335ab6fc97c7b6f4c6f096e3c8a1684eac61c35b9aded6152c2d7b5b57d2e82f675b92650a5a3931c618b38e517254421cc284cc51de74f1053682727a5e6f3806434e42d61c63ec202f562421050d7ff118d7755d43c8ded8dd388390543cf86fa781008131c14587d15be9baa494524a5eeddfeeee4724b9682a1dabfc9c238e12b52a84752c6a2da3db53db21f21bafec92c6b81d63b6e228526ef6ed7a6834f6c088a03fb6e327110b30ca0a19bde72a557c7e60d47ec3d89154ac20c14aa6418d0cdb290891ecad1660d47ed93fecf3b0c27a584f41b4c3c3fa9d82c0fa5dffe257add55390d1b56b1f8db2ae7bd6d52118b56f8efad0336a3fd268cc0c825158961dcb2a10d89b9440306ae7fc35eb0ea33807a356ca8a62d43653d8b3af98c3bdeaa02463db1c540e360a6279a856075cc228788f12b10efc0ec13a72658ff7fd0f3e4000619e6b47103b806ccb07c2e088d14b25120deadec347a811976d14f64d361a558f51903d4a8475866881f76d1a1d6f012346048d8820a2653f30204168e9f86a62db1c2ec73d1823f6fd95b50bbc8f8bb999f45107422e2ee90d355de199800832c51bae06de97b366747ce4b8c6302a1ecac0cb41e5d8e98179b8fef88e1f980708e639c12f10e6e15b8927d836e783541a23238dde13e19e205807ebf1e1d61e48104330cfb5711de9fb1e3013866144441dec9b05310a3b825198181a7b0d13b46d9ed7ea8f6d29d00ae6c1827966854f18a585f73d2262ff328e3d5ecdb0ecdf1b12e1da89b4460d0fbba4356a78546c8b591ec30e59b0533a844618458d6014bf448f60149fd471d847342806d82115cc831d3b9c82793e22ac9f388cc23ca32a61141886a5b49ad5ef0da92cc38ebf5afa0b423576cdb97d5d3362973cf68bca6397bcb0eb9279926879c55f978c58c7c835ca8ceb19a5c1b9826d972f29fc765b85b0661927ac5f120d25f6154761d44a08bb1e844d0079d8cf43fa04e4012e849f944d007980df4ea2f79032e528bb54fea23093170a76772f040f3ebb318dc1f506712d46ec19fd4335694fbad2f11d97a5e337ba516744dfe858bf20545f9f8d1d3ecbb0d6a45c75961561537427af58d16744e9d805912a803ccc5f8f745e5446017988875d8f59651446c5a802c843fc4521bc94535621ec19c72878e6f956679e473a2fa36c5ba4720a7939c5b7ea29e5b7bbaa10d6928bf0f18c823b19cf336c1f6c68046fcf43f05b4f4156f6b7754876d08e960f5af5aa57bdf1d59229ef457318b52c96c6c25acd15357be328afc9a83d84752fc857e421c7f00857a9bfeb825cc826711739b95da3a9615c4c8cf7228d893586bb8159ccccfa9b94fbe39a5bce59639847cae69847462352d653907879593fd9b1c22078f0abf6611ffee80a5246acbf18638ca8d938ac8e4b466ef928650e1e791923a33632f717310be46c1194d3f2136e1967e45d5c2d8f753de44e8ecd4839ab63bfc4f30191bcb00fc3304ccec85b1631ce3967dc8539f0362607700b53ceca92f3aee591f2711915fbdbcbba9147cac871cb161ff8df72ef196808f66ed9d2bbcb83ffedee6e027a5711c6267c56c7b29452ca4b5ed7e55900e2c0830fcae9ebbab86ccf524a29a504b2e306413b72a07ad86154ce0f3b18b5cf5839200d3c28a38c11b53a565eb40c83071b464b19a5fcc10ec9666cab83c75df6b63b7497bdfd5b1e4b56077c09a732a8c449152a84a11e25215308d1d08c260114f31440404020168b87a3e15816a60f14000fa2b250549f09e42447629442c618440c01230220200032431203502727d42fcd0647cb1f40377b9ac08467cd04c4e47fb470a92d8d9781af1b3d6a68d49801e61f04d2ca48e49e88ba5ec19ac50e2e539989ff909ed53b2693a3faad05fe7f7b0c701808346a45824c9be00bd35960aaab5f1fed15a3cc53daad4350f201fe32f36da19e3338b469ba1cc19b00153547b9c81e2ab5f810426cf8ab4d7f695c337df0611f7186ba4599ed369b44ac1f3edfa19e106e7095d1e8bcf01df3f50dec6fa13fb7e3d55e235030ae80d4d8a0b14c8fb92c5b42c03b5cd355551209c7ca58dd645f0e4ed7a5b7001ff7d6aadd93eaec6391ea407aa8ed1be24a59235dacf23950c95664855594315c89a586302281fa31b4265e1da9623d87b2ca60ce473aaab4d2d99cb5eb54575d42b87765956a2b565b47a83957eb6a8a8d971a3851d62392e3630d2d59fcdc2461ac0a0b76d2ff3eb7875bf8b14eef77ed53173ab3d280859f910b13d64b3d94fe7b886629bba3d4c0f1ed0cf5ce8fe4903d82905e4b8df4f5c2ca5fa786fc355d6939a0cd148063ffe9402d20e64ad0c5b67e328c0a80397f43c5d488e08fb4db1d900ab48524b4909e0ab7d2a0f6cb17229fdd11bcc8b712fa95b74cfc14d5a127b57f43bf911b95d4c7178f8c24a7df96eaade87604c7286a70fdfd6f77ba362feefda095621a7f9ff487dc1f043a8ec68c248ec45aca7e8fd6928cf376159bb7d8a5ef39fab8599170f0f9954c844b1b5c365642ae4d1b01be616878702dee130c8df758b8c13713afee16d0c14c81773c08dd5b2a895373ba86119d597c5add10472089ed3c8d2d425d96bb2aab993adb76cd612756d27a32185665748f06549f255f2d4df5cade0d84d82ce3ce05629a720053ec3f913954fc94cfea030e71248acf1b42678c1d24d9eb5e78e29b89ec919889f5a887315656bcc7209898012bb7502359c89c337229fab47857c1639d4bea106c61986d3800d2183d055883fc1bca67712c5f178b66c29d83ec2b2c80c984be54e68515ecccd28fd65c3ec43a37ee04c724292b4c6ffb01eac662da254fa3e448399c2bc2d9da936d6dd987d5b65ca32c362023e8124a1ca5e27043cef9dc3bd31a57e2909c39c09835d6d21e77e7fffe04af5dcdd95936d1632d9cf3a3ab9df69292e47ad48ec6ee84d491df9f75d125231b5d16ad74aeecab3ccf46d351d223fa5abcf136dd581080c5a85ff24f4e19188da0b67c2c367b258bee333abc3c63f1f40c086f9d4662d0b07162795d9320df931b6a559cf7144957260038063e59d49d9f7f7ddd520bc8e1464d1e422be92b9b1f7f07c20646d9f783d1a940c1f1dfcccf37d735bb7defabd6ba5c1b3d8a969dde91bd922c9657a3292e8c0a476627dcaeb42c3cdbfe6b26022434dae891a4eb7e66b9548ccc5d0a281a098d734d55ef63735d67eeea76f761c5d0d4c97ec362bc59538428cd6ce168e89dc3829801c1c28dc029f6b1668f6b3264882b1fb06632e4c2f7392eaf9b0c3d2e6d694a10e25d041fc9d04ce7acdb54fbb1b0927fc14bb38e68814c22184402d30db5171e901acab05115e06105222bf81c8ca4f3aa5dfea99f2d7676fb3d44176200a1b9bc5f8b82c9a1c8a211e912b22ee9ae03f2ee686c9dc32496331d94ae15f119097b8ce73a61940fae1aa0165534cd3ca67a5e3718510af4f9b52a21c23b6d29e6114322ad98379bc3239f2c34247cb88821eb2694f03fb54548774cc6e3e0ba0ecfef80b37f8dba0cb7e693e96521c160ddff4f9b2a8750fc23c5f571ba1a98a69bcc7e2c7218fe4f70669a493b2c5b377737973f6d88d7f695cc0b88dd7e5476e80a63de34c7354a2e7e77ea8a79a9f96a0d9168c3f2f2981fd3ba3228032132a3c02d024ee757863333464ee5e84d9a50ce19c8250c3606c1c01aee6bbc17d22dae228fe7a02d3dd6e6ffcbd58b55812642de07998a3b8fcd0d7db6cc739bf9477906620acebd94458a0175cd7cb15858aa72d4cc94cc4646db89a9607b11f78866d29e585dc7f9b1d21f366f190fcf707c271a04936c9bc7ee5c892bd4b805fd015997e704363b87facf269a68332721b48611330b4e49912a97087840cc6aa2db764e912bb80e7dec0ab3b1b4819b065e5f06a9077a7bd4aa48a285554c223610f0fe68970607ea83a58529612bf250bb85e6936cb50e570c082ff54330bb7c6a522883fbb0d922d303a9a21c674409a48310db1a06a866f805362b31f18fc83ac8401d1b34fd064e47d0a0c8dbf5ef05947d5379144b012f9a0b41dec08be34f7d1fbedca450ceb484eb4f24e71963fc3917ef0bba869f1e62ae216c3cdab1788da62971b95f1a328a8f69a480d07ae230f6a1983839c26d43146d7c33b4bb04e516119eebdf33a6aa2ef1b1332c2e20182039628f66a0479b90620c737f0d8c3e6b0f5ae202f373796561c63243469ac03c1c9506d6e819a0ffa1bf61ca91fe60c7ab1e3a97ae171fc1fb5df6a8eea99b8acb4567bab22b75829053bc87c16d8b7237db35a04391d28488e984e007d5929bbfaeff0cab958e773fb66f118925d5e08c3e8449635e37a9ceb0d28ada42f0744078ac2d499fecdfc4b126e86f9b77b33c8774a3a0694ae0c1b49421d1395d43cbb519ab1a023f2ca22690a0d8b0ad9b5b32692e29b3b56c5fec087553888f5628b85ac2dce5e530897548f81a6c64da3a1d66acf0636f224505b23adc295a362922c09ebb043840f358b2ccf8ec0772ebe220818a6a97a4c1e2a2e00e9941044ec285d8bd59d096243a591d38bb9327d1da17aa95012c5410f9f4ca4482117d11b9752ee6fcc9f0069707f9176fcbd2e2796475ab07fb82879b7176122d0970892e2d89f7fc046eb07e78d2be97d964ddec36e98f91b7a0ba11d0484c31971e29593435af97f3d03722e263088457299f57d02ac631641b941352ad45f9550810e9c421a48dab6d1c32240e666511c866de1bd65c85b54c9148246c03bbb5530bb14ac43a18693ca4032639a3f1d74308a1b0ec4535fce649ff532b5f83b454d1b22ab8ebcfa20be0759ef21849800ac90ec88c8fcf5abc43a47c50916e03525f809e282aec8c6f66f64759ada4d8f177f609da9b42acf7ddcbe65170787c9e5f171f11f48505911b6cbdff4543ca9bd0f62e0eb5288a7f51dd549472cc7185f9218c1b304cfc2242a99d0bdf494afdc91bb60d4046d92a47f951c8e67772dc4d2a38bdbefa9648eb9fa8bd26f23d71a9dad6173376ea60c894b2b3e185a7fa16ce722cb9694b240045dcdea54e025dc4155fd002073f38562e58b01cf1ec757c7baf7129c1b663ebe5ed26b7b5dccc09a7cd87e1929558e47bcbeee0d40b748dec1fbe9006f5b3c4fae5d7befc94267586628a4d4992f98de1c21626c90bbc6202f71925f6a81a1ad36149b1667abfc81c6c508d9c82d9e071af08c3eebebe28b283b9f51cc0a6a37c40f2eaa11e78460dab78f598aa08074a83643862bed2acc109959ed9926712be8d01a1d352628798c458335d55c00d160a2a258a398ab24acf31a769da2e716ab89d10d1054d5dc9c46007b70c9dea01e5ae3bc349080cd9dfd5164885cba5c5915ddb0195547fca2229ccf2561b707382a5d622dc6cda22eb830819fb5bcb153d002bef77f07ed3faa49242b75ab4809dc2ae780a4abc792ec86bf8d98144e3dc0001cbeab56f462c8f2cb5591cd6cc715f82f6110ffeab88d1fa12688b2407b300a5d0fc898176b91dad696e32e4aca01e7f36ac834b326190500c2eac63afe9bca72df420df844050717fecfc38edc6209b3f832a7558954d6a38b276aa6ad51f52b53bda1db16b4e19e983a45b8086689bdc2282886e49732c365a7a05dba5e1728b0c6719c3f177a016ea4d0fbba227e4c99fb2a26ee10aeabbdcf3fe608cff44abc865e097159931865bc40904db521a64989b0474a395b9bbe32a1f53caf170f4005a274eb945c64d1c7a1c35b2dbc29e04fda0fa147438a66de51043ce43298cc82c36de9df62d68aaca45a48fae50ddb606909705cff425b25234873c5798f516c9e0a298b83088b0ea58dae35777bce2dd4cd6759241b95cfcf5f3e069a4e9673e13b625b2f2eebf93f70df969d458241a2be95f8ca27b414fb9c099a941b1fd73bbe5becab6ae9cfc4bd36e6d0e73b02347bf22113f3795f98b6946dbd2697243b6d4a7afd999221715d1f7d2b5673a5cf1aa4f55aae8e3af8ba3686fa17bb288e9f6412d9fe1660de53f2c0653acd2c0f39a4d164b2e26cea96c88344a76d0b520a3d2fb986f140a107306beb8dfaf4be2bc906e90c2d862aedacb3ed3a2f47cf28645456059a15d6abef455d213cbccf78d731731a46ed7c54f8f886e471e6c197961c94b549fa9698532dcf9f2b2a5a2105b2322fbed68aacac17355bd6f6023d8d6d48660d68dc0168754988f74091a35cb11390c0006c4de5670cfe97fb262a1cd33ad160dd8b64a9c0980d05806d93940884cbcf73164e85b79ea4ba5e657d3b4ac487ecab443613cbdf521a10d8f1170cedd1865a0711282f6ddcadbb9a1994e08d5e0174c2d9263624cadbe1c6fe0a24e1e31a7f11bf97f1b9b086953098fc1281cda3e07076789449c4e444767da30034d02cb9159f63d53a4298eb3790af939972fda462d85b5f7b4b2cd3631e9b35c29c69b360f4c2dda5390af96988c0eca8ad9e5227f94521c0fae36e03cd1b40eed726a994ec5f69ee22b6cfde253471dd65a4fdaa19ca167fb668be222eaa05131d1d49f58276d4be53a843e8182f5c949a19f79ca50503a7d648c28fa73abd191b953078ec2bde99feb413cff2f5d7b02c9a70c2e3dda54d803ea1ab53c13cf3f0d80a34ea139d48d23e730b9d14bad22702a09eebf8ce645bf09087cfadb476e82f2b55e50287b366af8723c5c364c7f9f1354f62735e64fdc42a4b79b727df712980b5d6bd87b0dfc61c1805b6053acd3a0885a99b70519b3cbddeab9bf9c06dc210e1d492f93ac9cb2198a24567f3fc1302e85e8fd7c04a2634af3c72ac925f42fa2410fc709c27a8ab8a1f771f9ebb8c4519492f411ae1f68f0f5dfc327f7bb7f4fbf6f0dc27039eb8bfd7e1873792060928c4c4d34edcfdfb004af79b8afc5224e5d81b83e0ecc0552f15ef6d95797782c72b13d26bd91af1bb922f18965562234fd68af700c1170a629f4a0e24fb44b382368d24541a9d0271ff8c5dbfd0bb7dffff81867774bae791eb00006fc518f338d6e0e382d87636dac4a793684a1c7b91cf280017a29244585950f6a871065d1b76867236440044ac92e45a23316a62ea1983867ed6606ffae86395c5304aa46f9fe38c748313cb2f35b8d13b243b848a433efde73012cfef3b64e3720bbdfcda56abb86ef2c5b272f0912bc8b604cd243d02a1e6d1eed3e67076825f3a4613f2241e5a4ce1cd20622196862b05be4b7b4990ec6dd5dcef0411d282a9a9664828aff4d9f5177204d5e5a0e105c36144b48f290a69c10c3c11b454fae9dfa5c349ab5a31b40a306a4b08c0786a2aea24ae306c8ca1ec8e66f263b2246567d58b47933d0369e85a46c866bb6e56b435a656c12296dd90d830febe2691264b408e996ac3f959de8834eeee8c2289057e89b1347125a9b1fdd3a981bb40e21a396452ac4d8fa64472689658924e9bba8751d5beb82e1e299b2525857d077b828a56b5d6602222034ccb8fd4c9fcdfe5a290112279aea9ef34cdcfbdf7a10f62efe4ad0b6ffe6fc6da1f6d7eee5d33c6843513fab04b7289e3182147ac1cbb453d399fb570b885959cd0cbccf927e560a9afd16e1c0fdd156273f61b4f94e89eb872b3a6f53f9abc9106a14f8e0e2e5351b0479e0df0856f88a2adee48b61719ca78b944f65c4f2d2b6f4a2b5c68c4c6af73e31ee336a41f9cbccbd2880985852a99033434188e4946bdd83a9124d91902ebd4be37b2dbd2244213c3d9384ffacd58ffb077954a724220c7c0b01ba76602d5906f65cf321a062207ea38dde12e51a44dc7f214408fe185c0071e297fa63d6589ec61f3ab393ae779c5c72a9fe8a232a31c2dabc99855338a55e26b1122257f5dabe17b3f008e5857b0c81d2a4b5b776667e52783935d6d094b15c51bbcff0afa7dce06c95632fe1144f4709f069e4fe754e065a8ceef344bc5c297362c991fe6c82f1798c59e1611cb6a12fbf523f12597ade6dcc89d54762db942dcd392c848cfe2b1a887338f7f2104c9d1a6759b1bb29eb5c1bd70d7aa40dd0d654fe6548a4d5c98943c34a29d881af7cb9da073f2e14acbfdb3df4622b314acb66f976d1cea2e1e73945ee25e56d9a258dca76ce5f1f75d150a28d1820169291f8608127ee40a4862b7a7a31bafe224bee8062fa542fd60406c81339e658aff820981e7c1197906c86d0d2590511b2b219efcc4d2633f4941cb51a996fbf1d381828a150268708b791cab8e7b777ffef738116180686df184630c040b9a8e9ee6461c85e54e8442c46fe14194c50978685549322431a1539d6ca36c504a05b40321df1973aaf6757671bd9960535c40d270f9387f9884047167298d62d8fc030bb5bf0da1c98436943a0790526e5116144006023f88660ad65f24377cb323ba0b4c96c7c4fc4a23226087856eaf0201842b067c1c57e4573bc53db9b5eb4a72c4c07c1b7dd6f5ec421ad5b567a757936b899f772636e237da08bc8a611ed7c4ef9ce39f21fae96fd6fd39023b21b7c2bea1220046a74eebdd4fa8876062975bbfe7fa6da7df6e3b066a30bb1a0e63d35c077536afcd231a6523663e682dd25460a8793918718f368252438c36c70cdbcb219e4195b854d992342dfc88405a29752fd6cc54871bca18e63633eb6eb43494b8124ff819e27149ea056a8efc438ec5628ee3dbd7f726ed165dcf4c8396a3a8becc1e2208d1ac1fe63d319e2e3c832b2300c87b0d57ce3bdd3210b0a3069054a33f8f10a8c74521c77312d23695dbab505ca63b0f8e01285939b74035cc6c8cd3c81acba2d0e9a4a93340d8c656734fd4d780cea9ecc9b9bab6aad7f501e0330ac8709d1969d730e3471deeb9513f9ef6dd5d9650b1d341a55224138c94d4e1c74f9ede9949fceba92304f8b9c5b8fb35109a233a5bf48ec502f69935155a277259e12c4aeef9386eb9fc54ec9869fbd41a93e1c032d05cd2440122e16d1d4408ea431657260cb17d0bbdefc7644bd1d189909dad9ba4bd028c95fe2660df84925f6abaf04c8c65eace568893aa7289f311d8410cbef42c5c6040eb90ec6e52f9a7befbb54a75df6dc9ac82c5e67c3e6e59d8aa23cf37c91bc8c65d89cde5b7c01945c36b323010639ebb9335823fc26246215ebada39965d1ee4a4808414746fc654acfa2e7ee15f3a3bbfe31f2e45e321eb6ed550ee3b911afb534494dddecb3078ffa1ca22f60329b143fd79e935a5ba21eca945cec5027ffaf1a04dce0c7808ab8172dc027690cb23d34f38c100e33c76c0e687a8a1925686e97b158621b0732f14d28504998fc70b5d8ff96948fc564d26532cb7a9a605605acab1fb0979edfb7dbd7999536bfa3232d46d75ad9cb88d178c3ba590b8eed53b16b4680c24b40456efcf18fbfec1814ecb437a470d48c5fe8b97185094ae5a6c5b4f340204b60660c93fdb1b8a09f807f03d66df5b319a5d3fa9a1ae02f48c059c2cb3ff3249477508a9e0d3c7d0538baa722420f2b1dee4a5c831f4d6323481bcbc8024c1fc23a8bca3556fb107a2d08b908fe5855df292533d31b17b4e12511227dd4f6727d801c75f60162667034a05070232bfe054ee8964651c73798a60bf2468b2e405fb78f3f534daaadfd7400874a6a0ba86cc60e81dea850868e7ab883270588e6cff921b4fd90b62db1397e85ad4757ba7c71ae29a65881717c7dd3680a684129623a9569474f35a7f80a53933433c7a98dd74a796c15880db5be5d0f60a17bed8fa8e325d29048196cee606304b8d724ff91751ae75e3e614f786104847b4518cca746703ec1c652dd80d72c878ec449ffb6bc91be44115a6e918cf998b1811ec4517512b39c2eb83d02e4f5693a69b5b529ce18f7c24aec574729d5f88b7ed3fa27624d5aa9c358601b3ddb37368b726f46ac1cda6cdf26b261c68a68935221c7f368985aa511ae55104dbdbfddd9fbc24133afff2d2d8b8ce0c797e30a52b824b55bfbda19cc5c868a0c6cedbe551afc78a05a8744acd972403d008ca187de28f71b0e6f8a0a88e90b4791eb08f933b1d42a3cbb1885d571d26641f20e27b101fe2035f4e06fb1a866a1c1d7be44cfad411f980093aa7db028411c3160ce55533199142df524d6410ff22237db2be8d1923dd4466e86d2ca609be8c0ab41cf523b859aa91edc62b15e512edaac1b34558b84e7d1fa46a8d2e90516fe93114930d54827412b82177fca2a9563ca38bf86b409fd61742c1294d9299bc51483637d98c3cbe79dccac5fedf04a752d1b2ae29bcbf48ffae54f61748baa815832a87f9441017494b2958a363ecc359242ae212436d572b081120f0b6b55929b034df59518a7e71f31954890df5fca77d8496b250be03b1762f15aa85beaae0c683469c9dd00897d4f7baab79d75f238dc3c5e20e8050ae9697596efcc5dcd76e8ab88fc7326bbd29b950f814e3a97ae4233e1fffca1c3ec26406f4c226d8e6213e0f85172cca241b92fbe368035c0a596abc901eece993b46e1ed94b2e19c16a3c62d97b59d253d8d11caa01e8a153e3381264e8c162185d4cb0ea3bf690295ed5410c12b48e9d104bd042d8e699d43fbc0a1de640695228d7301970c82dd1b63d2f6d509f623c43ac7b72c21300600dfeb7712fdf0bcd1e4af854cf95263fd65e7171f1603bcc444ec54999d5192ea55b25380f4b91af831bdf33e819033cb91fb9abd665e924ca93b326f0631cab60477d478a1453337f5f47e1b64a8f7a9b4a0c081cfb388698564944210e0e54c0572328b7148b0f024010e7fab9b4305d0196f21ccf5b8cba8c664918cb2f0b3ef4ae6d5b6696328a58cbd3b77503b8d61bb9385183be5814c0e38e39de149a6633fe055f19a27fb07e1f4f0ae2a88d5e9413542f35888de80803916d8207f072bcc02b8211c330637e530f2acfb8ad8722c17af1a64f1c804cef65080ee77aa06bff5d836f0ed14208b50ec2e33ecfe082fa8e1687cec7b722c023676d8ce126da5a15860757d0a02e419417f685e9762050949aaaa9507fd87f4a34d366e1d70436407124a8fbfe07f9b6367580ac6228718e45b4f03860925047f1114a9c889cd399eb7ad9ff790428e6c531cb598d161f2c37fc712dde444c138be73d51439092db611d85727b83d8c6b5129f45c238aadfe20a1a7f51eca7fc371c1eeed54c2acd0a0734969f06d5e764bfdb2f69bbbf7402b31d77bbcb4d26248c32d7f0191e9d9ce95386abab613bd8061c8cbc809a5619c170c83fe8c91931eb7cea191d0c87a60e1c8c449402cc769cd96f8350534fa8c54bdce4505c80d727524027a4b95a1f2804c1327ae49ecc879b261891264d0c349001a67e6ead747cf8b377d0b3e68cc41db2986848675af18ae6efbdd64f61ec914c8d0ee5f7ddc904ccdc6d0a185de1a4de537d0c65a50f39a9ed1fba52086ae12307fa73c1ecc5867e18b01d5191c2f4cfeb99eac5078d7be1b13647fcdcdfe2ef83952c69b97a174e55340cc9653ed9f0b93c98f35935928ad50a78e9c667a01f652d7c1a9d6e6f5bd194d831a824f0d57e43c81bbb05eaf3b69e06902c1ec82024a77d9d5168b9ca0dec0cd30033b1c1e23d188edc3c994653c5a8c5c5ebced048d3d2d4b0a1923a70361ab387166175e0633b805172cc5d4dbe5df252bc7ac6465daa847b024fc0f1d9a817cc89914e6219ffa40b0b2bbae9ab7c8a8a6c556391c10d6792e643abb97e23e496dd1e8e4640912a4d54e4e5a747a742903cd2e816682cae7c62a987045fc92cdb6e705eeca1fd72c62a29ab337ed2cf1ca632c2500d0ef6ffaec5b5b7594071ce4540ea6efc8e9f3d59e041eebc35ee7b8f51fe284cd94a7b7070961ec0c1d98ed44e783de09d7ed288b4e55e18b29fa7246a9f804bdc589d551b4d251aceaf149e4202b886c0b61886ad7f00ab8cad1f401fb40ed36f0ed75f1f5131941a57a839fa1f4c9007a13fbc54d75f341d085d703b812aae741f49ab79949083d1e10243c5dda2f6b88600ab0f33b0dd2e63661f4b75ab9056321d7ff56184d9dec725fd8ebbb82ab95950652a7a89ac5943f44dd5f9616cc4482d10feb05309fd4444f01a427e7c7312c0136a693996c5d4103733409326586fa6f14c0074f433255706569ebbbd14b21444b8a05d618c969bd61c65dd40b080fde4171439dacb3994b87ed06a27a2cea2b29d9dcc0342863328553b0e6a49616535e87213fa1339d9d3c1f39b8773d8d0209b34032db745ee83bd14d9b9d0026208a188d905f6740698dd9b5da1f4d47ed565d9c9f83fbae79fed0f68407b4267256bdf46639af4bdef3d31b2c62c21c1deeb47c44c2010e0994a1c52dec62a667d56684bc52f9a449c063302716ed81336439f3e4901bf0d60300a9aeb7663bb9797a374ac27b51d0b5914aad956f2454ced0494baf28a141c53c2cae730a31855ecee4520f37b99b44fa77fa288e951062e2a6d75acc35c0f4d041374dc860ff5cfa8c91950694692cf69c3879b349d5afc282dc1083699329c378f6d1a6b1097e5c8eb0ffe328011f7e0a078885c0e131c7ffa60e298e9badf6b61b70e23ce99f6cd96715c46e46ab3b11b2a73cc0ad452f9bfb94c3a09a65e0704e69c2ade4a64d86b246b3ddad6431b91c0ef9abe1260817c2a45bf6716e60630258243ff7512cb93387a6cb6a6b5505b492aaeaa748b2a396ed5048dcfe0c2ef085032de90ca2fb145ab01535491196a79118710b1dec70c161fdc278d7eaec197e3dcf11cc9d3c3bd76c63dd43f84822884a6aad69229ebc2b4aa19dcb6f476696761f502c6f16b6e9f08650ab321ce93558aa310f50e45e7204062931249a0b310600c1473ead15cd2abc1a81c573520b6d74d25593d41473dc421a9538751fb04c230369d7e664296f787ec4046c8ea4fdb1f3aaea4adc4addce93fad7d8b0ef2a68dcb9c931d2d8dfcd91a957b6725fcfc67c1f31153c22ef357f9ac85975400b8971671672779928a4d6c1604c5ff7c5726d29561894684d5da4216911ad61c4d455d4c6e594c3bdea463e7c8e6d4fe51585c1f1b81e643d7359f32d200ad81e1152f57461b15acdde160085f706040856dfb1f671185c7878f268339ff4484446b87805a671cedd805d9a5cf435354b0a00db89098aec6e77c1e12b3b04419d3f675759f77adb846ce88d856f5d9f1b8aef662b61c07f9ca1df1a640df0ac87fe289f0ec85428924bab85b218eca12c040efe2930801a9025e8fc247e52983241ed553654fa5cc527c98d59932a0e05e7f2a69fcdfd76b4a9b710b2239be95cf9b015750333ba4a9df127ca6c3848e847aae74fd3b9fe87372c13adb13b9445b7425a8b97c814fe9164a7567690d2cb4587d508c7b712cb42d640199d5b679752fb01a2da1d173afe601d80314d44541144e4d4e81eb4e885e0ff6e8b2fc7732a03b4a0ec452226b709f69a7fbec484ad1c28a3d9f7140b00e8c4e1a05367503cdd9f628502a0d16d65a5d24f24e817fb4d38c674aae38a42d1bd626c2086a1fffcc86932de6a98c9ad30b5579d9e798b5e1c2d30c4b393a60d64ee7cb84db9309f3cad345dc81ff96adf65583125665b28222b873520cecd2d36772cdb034a46da2460f6f08223bd3c1a39f44bec5b79400e25196dff8f59cfa32f8b108a6740eb777a0f09e5961e5aa94f9eb18ad8b482d09d75eca37ca1d34f924e4686f92d15b3c04aad0f429fc91cc3b312b5e10fab4058066cd87cc818d596da2fba5b9e0764dc30f95b90e0b5d4a312da6ebc089f2af8456357ef24926260672729f3a08673219e4320ef1d4add0e722a488ad036ad8a47e68302ea1259e1cb5efe086e8ab9c366903976504ed973f7dce0c7ed3a2a25f2e8604910f63cf3a9851d294686c426085ec3702d7a6c9185d2ee5e54dabd1794bef5cea5ae5ea8d4bb7a0d97e2985edac17892222031c5bc19298de2841a1754e399ce0ed289fb65a8e6c3912160e7365bdb33ee05d8659c58acfc9352116486482228a60516f2a82255043c6ec10fc7cd59f74d33072930eb27c321086cec4ed4b14db77dedafebd7ec6d170f4b6c8630e26202e2a52a6d71e615d9c90cc625562afee613a80b23478f505dd072be91916eedc9398a7c00be0a28c4e35e6a7368472964e2b3b92f2ab2f6f9f51dbdc9241ac424dcef497b535f9237a470a47336e24ee0a8108376544dce44872dcd18b49603e6e14a2b6a437f79126a0bde7a6eee736153a872a1bca0a15037b99c2ae73cd59a42bb084e4949f614b9f2f6245c0cbe3252233d11b587c9f310dbb881ba5aad6151e3f1dfb2cd90403e58e3863aa1b83ccf6847dd5d2cc583e8a274a99667d81f6acf05076660361ec913a296913ee249ad10053e109c0bde17884bf3f7a98bd4445510c19b83507ee145811f93b8b5192873cdb602825d46eb93cf1b2840165f2be6e0f635a1d7a0884826a3016a877e7869448de46001810adf6f9e5e71f0ad0edc3b3af336c5ab03ab00589818d993cf577051cdab3ee249ce83ead4e416c68416c73a2f3c780fcec2adc16bc81a50a36314d0bb1e761eb472269c94393b08a4261606728281817a0b162c7035390d92b313cf735cedbdac4f0c67916f4dee1d05c3f62d64dc61c25e2ee3eabac657e6262dd518f069230743884875458d57e17144fff4c4912bfc30e9a43fa6e0457c3e5a5c8682dd910526ed3d4b62a67e2d4086d53950e8d44dc6100e80d26bc6bb8f7518f548a77baa207adc0e06c0e9a69559d21ac61d765cd4bdf225adc354c23d8e15622c98cd600972afe84ca6fc747eeed45d13e37a1c3814f817c15c781bf419267dbeadf134c93119a624ac674723ff13e44ece6abfadd8fdec8c7264f61dc1279f99b89e8ee7d8017c4d34bfa821648560e8de562f1a3baaa98c61c1877a1490dddc4f96fc8bd882ae1e65989f223ef5281539545b38cfd8296e2a08e005a70304114d8b61697860891ef677c410a5c1b9ac9b7d9d8a3cdb9018d34f37d9d44039580c0666c1b949b65ed8222906c2e7041312506d1f9cffef2213bf597323cb51f35a5bdf9fa032acb156f3fbaea2038e20bb9354582a02af4e01e006a631b21cbdd47517fff83b3341a7db88162247c31aa59119e4e63573b27617abae11d4a51066aab777af72b243ac4f69f5f707644dcc55c1d8e5fa6d87870cab6098df8f9352018a20596762185a30e66548d0a1635998bf61c625e31c40689b29b32e9918ed8a955f955a8b4daf75b5cf81f1da3dab81cd182de0f4610dbdcdf286b218174abd8db030739f4896b9a4e83477cc1f647d72dcfaf01c48780cd8cc616a6e6528d6f88309a78d4c3969bd3fa55d5ec31391cd6e628382aedc73981b990049c405443077224411d99c9adaf9b8c5c0eda302f55d18fbb7b50b98803df0c08c6c82c6b7fbdd0aca183dfd9010206c35b3d2268f214261c99d599c598d15639ad489762544765ea1b1562dcd24aad5e8fcb0a7dd75c44d9bc8424d9efb8cca288c322fcc815e00ab8c5516c6baf73d060ee1cdf935ada50556efba63560a9c17bf7f7e00968b737d0108240b326c5956da08540bed8d864f6e4d68de0422d13d5faa5ea139ec817efa799e442f4766eea513cdec7a9407169c95388299a15945a357c78327b2bda5e92e1b188e093610416c5ae9aaa15f78f85f40d0e01d0467adae888be0ed353a55946515d4e9ae9b363889df44d0dc6c89fc86dfa438d13b7d0e06d216ad473091cc966c9f4b292e8528d2a6359d7c84cf1ef071f74b305fabd637be2d638c5fcc49797b1519423d56b4e435bb1c58e107e2677a0be828c852c4d9a40cee68460ab7d8612ffd45b189119c386700b04190e102723bfaa6c5d74bec860f21cdb41b6fda4ee2394971142b984e97e1d1f9d83b4533daad53697960ad675ee29d366d8f76cc1ef6978186e5fec8ee25d42a49ed8b9b4b690eb7bcaa7cb5d2105ab8f9d9dde8911e506b69a7924c707d05885bcf9a82209a703a99d9ad90e3ae5f58633b650264698378de21e7ac0d294d65abe4b1a48e1cef6cbd39fc56cc66bf16b00743368404513ec171b68471d623f4bc684f1eea105a5ffd7e773b0630969b929a96fd1d99c6fb4d4de45166da0c9ac0a83aca81f3e0b9aeb09808a0248b19caccd78a08a53d652c7ae1902c7a6f43754e11afc7444591eca4d6a60c406a3af0f8c7dc0beadaa2794f7a01233d3162d4b056b73295d33259708887a53f4118743534d5dd49a43bfcee3a1a563bc1ced2453e18fc3d86a17d1d0b1c953a35454ba838f68efdc81d157821b7843afa095d822c748540f6ca247d7bf7e0627cccbf2e2e6579a0100a44c4d1933518030829d88a53f50b49e121be519e547e404689238718fbfc1202c0de8bdc38322239a88aa793d505d6cc6358819e73444d1be0cb5895529669f1c99e0433ad0ca919737384881b84a131da06c7935a2098780b355e8275a9001ac98bfd6a1928005881fdbaac91f6ebe2bcc2524d2271f658542f207a7925b20aa36c69804134858932a78788743e6b28c5f800ec1e983ebcbaeefd7e6c19c75f943cb47391c2c91b5c248f56158483785ead4e574f2720162c14988820f8dec1b7c6f9a92f81566b05a2f0d7aecc320a9755fda72806bdc3e359521b2362370001dad7ce02b72a02fec9c6024401bc21838aeccfa76389650df27b7b03f5563de13d7281cffad4ecc8845cb77cfd8e54ab92cf0a31d1fe8f6facef22e460e3f011c86637aa22443685573bd6c784ae1bd9259b7a405deb18e0fdbbcb8cfdf37b3655213ce8c6a1da6958e0ba0959291c7a69c1f75544aad2ea8525a9de574b4703bb3f6e64ad63dd7fb4a13e0d849c0651815588e93f08778efde69f0c48ac7e3f18ddf8290a0349813a176fcd8f65d66402f088c64625b0eb9843042c3286ed4ce7aa28e94d2b7360f7b75fe7aa3953b964e5d51e95046d5eea69622ca0b554020b20c874a9bfc53b91698e95f372ad92a64e4b3ac778da41654fe808ea32755bc0b0d30da97db82ef041631038ece81b1e29e1809b5fec717a0b3a03c4876eb1631edc170b7da2aa4fb0e0b69f30ebae57c88396c6f44d17ff6d068336fb35ef5caeb502218a4fbb5eaea8927f321df42b55f37260b5f6aeaa17a10d8401121ab80d3fc320b3db8a3d4d454709acf6e175e471535d688e024042b179adf727b4f88195d2c38245817a8a97490f826986dcf7343b68136312e80b728272edb5a7981b78ed0dc8afe9163a415debf0e651598a41dda60bca1077d00a638a32ef6599654f26e12a7b27547ac55bb8f758641c860dec07a041f3d098bdeb5d0ba6a76fce8bc1a3c78c43c5fc476a008ee8452f2596e00b944eefd5635b4d44975febe3e3f0a6708d45e60eeab81df34aaa6d2bb0cb4406040470fa63a23431fbae76dcae9bdf6126d877237d4454302de517d317636fd796cd0a53494be9bb0dfab4358cbcb83de897ac113d81a14347cf31ce8aaaba231b01555fc9921fa8a65273c6a9d546a9b59debc145b920f36edd9e3aee857307a170000bb09dd67399678bad989cec0458a4ac884b6f786b1e2c00c939972a8ce24ca915742a98e98449a791b2d5333dcff29cb91e56092b57473e64b6e590a0c428b30b5b9eb4dc411b7d0f8456338b3d15706128b491d9fafdde86c76ac2f0031f3342ee3fa6fec5788e81e674c35781422e60b05c2aaa329255ca65bb43fc9e555d70537885bf4eacac34c3f88e52a0fec320f1eaf8f29e81eb0254a2f402dbc40f1a78551cc90c93b7706b50b7a84125700637ef8eae83f052b786e7d8dce671505309cf905ee4a16c5a7ad2f71ef429b22128993792d93f25bbf2467092a8f10300d28ec340343d42682229e8fb676d90c1dd11cfdd39ab5262e23b08e403c61393a87caac441c9e49167ebff2dc760617782f0cd46894d17ab9c4999a3c04dfaf20b6d6af5b1912678e66c2eb95701937bec209176a41de96062b41d3feda9088234d4351ec33619110ac15f1b5f671e3181b11fe156e780f6cfbfe7e968759c004080291a022c49b90238be99c46a10c96b3b11abee548cbcc2cff12abefe0f618d0e923490105e3fd8d75b3dd5ae3e45994f6a9df0f20e80b5cd498b886773c1b5cdb2c4aea21e4948ceca7ac1abf6865bdda489223219e3e3005badf33f79d6b44a5d437735f074b3a265ad1634f262d5f8be4b6403e36a526db6c30a50fcd9df6c92825119944be31ac4674d002c19eda81edf8254096ac447f8481faac3164c421f9872b3ae625bd004a827fe22ec1058b4c6f9abc49ac9cfda62af15ae52e84061ceb12a45f85aa230fae77b09416f618a37b06b674564df4d1fce8dcacfcf259c2208f2cd586157340ef480a99844c6ff2483c12c3745532cdc94df55f2fcd39bb05c00a03dc93c80e551988a23022b7760d6ab8471785933da42e2b93e704a313aff5e48be34a828453185729a9a0dd0fd39e4e841b63ee50b6529d4cad0cee23ddb214cc96424e35237245841bf081e241307469d02b7e8940505812e3309b401173b81fc70304ea9e0b2e2343be76fd5ed393a9651acc751691b645baae28aebb5e774ad3a8d07be4fbe03d65ed06f31b9b946823ba01c29c30fd92de27df53febec3dd47cc2875bf0ae54b635ac7c87f45fcacec1ba2e974fc778bf96d2315d4fb833d458475983412b7cd16a350e6d67ecbdee8318c6ae17c3463bc0ff4899fea45d297af9012043092b885da2ace3d7fff49fb41284cbb02ceca32b146d70cf443746a271746d17b63de212f8df30c9f63a0615f42995cf0db8606ea359b0171993eb633fb2a001eedb015377ca56c244f4e9093a09d3346118bc0de22d1b962d2885b4cc05851bd2b229f41cc16994f7a6907382f219604b88457dd390af6a69bf2c214b545e10ce0d81824a9f863976e5c7c680b3913bfc11e2961d5d355ee97a4891e3668560dc378da07dc319107ef4981a1ba50527f4b82921e6296e9c94bae5e1eb95748205978bb7e2ef94ac47baeb162d0bdc1d008f6ddd338a843f7c9a69c69832fd5b5a470c9d08b03ec1dbcbb2421f991fee24161bea3eb7614586630ac62675c7d2cdc893e2c48ab011734c750ee238c22af9c49129a27f16b49509eb097b29c442da975839b93170c5abae5250de9ccc9cc6809bc8be03c61ae37c67efaecb86909101f5d0671a54325110586d3468b5db8c4266f506ded910952fe160b8298a78ab4d288d585c4c40f989a0acd38b46d63f52ed37af50970e365f0e495164e51105ce45cbfebad264e54c12b0ecc05776d87fa9c93dd8f4a253bdb58c253a0a096a01aace68d090ae012c04590f5d55c9a0a7e8e01a621e44e428c911649b6ffc07d4611e1893dc88a7b7da9f6369f3acf244428e837c024a40c72233c2075def651a879234d00ae26331a59196376f2f3b301052ad0afbd483ad41d50a5f34eb6ca875106116a0e866b0b83d3c516c36a88890b613393655577b7368b5f0a71428ca97b7b312c019d4056c0c057898f15b5834beeb77aac03930c0255d2cc9f4c3402d6c8b0e81b89fde3a5e00540b79c7ed3abe74d83f906fbfccbc81bcb3762771832e0ed351345ad27ca99430b4e8ad20fb096c8e8c0683d3b89454da1ffc1db15fe1a3be94c19bd7e95cfb85ecdeabef8705c0824ea245a6ea9756630ca2a7aa98bc4a118d8eb258d9bc56a7577d2e5cb5ee7c0fef0c2366e7996bac04d8d5b1ac51ba21160d261a97d4239476c896593b98ee5c0ace6f80ed279205287f0b4275b2dde81804334ae93d0c2d1f97986d7d0b421e91b8897ad011e238ecb73c5c046a6e427529e8b6218a36e644a020f91fd8d742a7847ea897995a10b08d9b84dfe7293c6b03207e1aceeacccfa8522e7f6fd658a5828c827941bbfa3120e5510a0f22bda7891624aa96139f81e59d87c38df03df5cfc1c41a3e03f02561937a73caa1c1c6e69a6e339b0c9a2eb40bbb9815233893e91052975090ea60675dbd5bba34e1151d286a4853d6896d2245d648b7b626b4d197333711c042978a85a19ba2855ca560f23fb2e88f2cfb97a90c7b7e71fb8f8c311cb60b7de91ac9602fa2af92a8fa690ebc560bad140d19a00627e9669f65dad16b8a9951eec78d095d46981990e422b3f5722832a9167f2e2c2fcb820501cdcdf24e4359960a376492ace8b9bbe28a57f4d2589eb6fa446d9609a626a8de8393abd49d90813974e01cb0283ef218ed9c3ffad8ea6389b245a999e6a1862cca81a52fcc505f253bee42fcca55d74fd4ca854b40872ca310b3abe8e8cc268a67b7fa0e75b92071a9408045563ade66dfcc5567c7142f0c57469f46535152cf5899a3793bd7d66f5e5cbf504c3041f3b6c948ec36cfaf4fc6b83c90fd8e7b36a5385d64b096c4140116b0e457b03c65c81b8493376936cbb4f33cf9257aba73f2ca5aac110166c18640afb220dace84c1b86be63eaa7a8800df532ad46b9dc730f26a1281bd0d4bcd4c0d3e4ccb50eabb027a78e0e39c11b88f6e90a5be5ade80a21968573a6acf5648889938b69221cceef3f7d1afde517c1c688f6b1b5661bd6c816cad8413b639d10991dd1f0a7302f0f6fb77c0a27d931291a92d1e78317a264cdb29a03d9c80b2e65c0d011fc79646a967c43c7ad2a30b5f1ce01428f7a2945597121bd6d29f17af570120e9700d728c21d7bfa8e9051d5ac1191027e237d9bbab04ec14326c1d4cbfa1b858a3016f0cf8180b93450ff719a8ca329c2fbec352f5db8c6182a504725ae8202ec6c40c72ae9eead3056e896e6e902c2c0e96cdc7417557ec1c7cc17a39e2b933abdf7224d69d96073e89fff4435a1979c09497ed209b358d5e8ff407cfe66f0daf275184895a9cd2b0cd65fdd01502fd2fcf443fd4c017636834133918c80b07fe16cd3c21966bcac4329c701afc8351665c68a3e53e11c82fe18d8110742c452f4d9177cc59c70f5b1c2ce5269f2594811b1fe158d7cc02709806782d214217da25ec76a46c5ea610277a4a8fc162f3f6400c4982dc68f3497c57cf2dc88d3d2c3f8e27d150c1a06c426d17d94ede5e5605d9da8beb502bbbb379293853e3417435c5fa3fdddd3ac16d038e3e3192606818ae4e0c0461ece8c51102146793caf512630b6f3463bd8941c5d08f1897a027addaca39e503b613ccbcb6bae7e64b09dfbc553a4fb11855b920a59c4200492e028092cb13311d8041dfed14f42e1ba3cc6a9ed3eb156d93bd7fdf1b3ebca42feb902c69108f26c5fc46667241c07bd105655f02c86b83d264d4438d703472bc483895f890f70ed7bfa068d003bbd44ea674ad16d0f5335779767bfe9f9e0c234b75e315b2c91f3d4adf668af7fc06f10d4307e814d1ac67352bd02b6e76248afe6ff01ca777dcb0ecf2d3327c08c4fc18bf90f8dfc09a0cd09606d5719caa4e60f2e3ac307c607bb94f4a71d62fea3254471fc7a08677dfe410b119c9cd3d355769377cdfb90e6e4937b16a8115efab508ac756fd63803c10157841754674327a071d1b4f3c252566c085548545945a835bd968dd2ce371507a6b3a7bf16e843d9b376cc46cd707d1cc70f54d5605e0ffc631300e3183f8274e185e9a3b53c36025661978bce499a5401ec181ac5031e3736bc5b217d26ea3451ee00a0fe9e831464783797a318d9966374c3bbe4aa60750aad26cfb7339db9cc2f90d3e4280bce148f0ea0ee106e20dc71e03bf0c876616b95d0c0baf480f62d68b56c58f97fcc540c39e6339cadb60e0692882406d7dbd866def23018e10191f2905dc856734c4e3c846131b8a582a882d65a9e660957ab3efd832ecdb8e0fae3a178aabe49da06c6991147df4ba7da43b73a39fa8d2a6b2d2e02e84f0790c2b9dc2a5b273ef27534050a79dde8080114c21bb28ff66ac885f8bccd651583c064434f48b1c02c954520bf8fcd109a741654ae8745ba025efe4dacd49d266aae6d31f6f6d1e41410ee68b9ef7b0310d0c6dd747e2001178182cb3578b2125a8926875e425182ca95aa8adef22e6161bda276224af2a4b566ca9315625045e3bce96dd27ef964a29e6c818a43936ef91bad24c9ef01640bb945eed285191452bea04d4c63ddd6bb1b41aaf6ebfb57a492ca3d2d0ec2f3068131c3e6143438a6b51883042abc50b6f433c76d132f0e78086e4a211421e416ab451c87012fc0b4fbc0b44846b236f962974d359ffcd97850fe0e93f436cddea14bfa91860ce32ca131bb096e7dc5c7f69e9d1d42b16c26b491d985fd6b641c12fe4b2a1078c75a2d9d86614f6ea7526610f35dead43dda8a0eaba35b06642c02064c0ec7e0033b298ac0a9296ef6e6cb288bb3d7c44f64c8213633514129e3b688b587a0ebf150cb1bdc4ae16d5924bc9809cb527261273217369023d4342551af1830026887800a0f099b68b24ce6cdcc9791dffd6ce74198df4e34db7e9f74fd82ab4a615122e1e6de8e7e67dd10fe1df402f709cd101471a70bfe865a5f6fd7ade2c8404f91deee99b42217e37d7f2ae3f08e67514483cf06c4eb1f198e2af9550582311af8696338d786898884c6427e74d2ca53e41fd331291cc562101ee660091db4c22f28cff2775f4f5f95bc6eaed81883c042dcbd5589c70c7bdafe3a85bbe855636a8b5fe912a69318d669bdb7ac070401251d8d2da8035f263996ce4197a78c59bc4e4da9cc27d476cb4586f30709c07adb74368a7d7389318d2d456c4628127fab300a3a81a21e10a289ee3f3e023db264616335d12b3a21189a3088b04c10c41f16da8b7467226bb980adbfa171c6221a6a36e89731486c7c84405d9af66a172ae3f16764984bf97912991954300366a89e259b6dc5d463662a53dbb77d126a332a5ed2d1b1611c09f5da7e7d41ea3662bb38e685acf8966d01732991d7f9597b00355de90a25898be9338e3330a2f29d8a019ff1e6094d22f10668df25c357114664cc0c4e414686211ea4cc40148967f222fd1d03c1525d419cd6fa3e03606ad3f5a4ac7ea89e749a06765c954c9d6d210318bbde195549ff96f4d6702031cc24b51a509845604f75209dc30040d73e0d2489b84f5e3d68663d4c774ee074fcd4b15c56cf7ce5c5f39faf84146a0b2ed0fb2ca4cfc930f9c1394afd601544f51741ccfba9cc25f4237a46333bbc8d14fb6d0d85d02351157df03d2b03a11003c12a078085461f1842f507043e4bdff954ba387115be0d7fca4a37d37dd637475af7e8c50921c9c21586eb30a70a0a98782e819226f0da72a714c443f90e9da10ba85cd29e870d3d17f009150219566538436c0644fc1d5d5bd7d84525f30f31503f0517ac853e69a834fd7d7f1dc1f9e010d9386473f02dc549a8bd975c1c66b69f771c94477e4d4f46010f14717b0c16685a37aeacd68407885b3c4abaa6208b939b7953da5bd0535c6ddfe7ca1dc2032bcf7591b7180f7e270dba39651a137671a31ce489707763f24692baef7dd6b9283bb4f56dc7137eed1b7c099774470faf24b70e8886c6a9d99f23057f4ad7b95af2be7bc71b5fac89c5ba6d481d0afb7a57a6c6f83e0751bb215e1f62ed217fa36e5531398c40c3711bc5f5035f7e2d6f0020a577afe73720bee1df5e0ea54f4adec59e40c34ae1b00097f64c4300dfdc4fd7129eaa6e14b55680b20a4acb59ff259fb56a353be752f2de34c0413b1f2de36c809eaec242d8df3890e2e5ec5494ce344e0a8be84dc6a991a2ca2babc26ec79da7c3e0b5bc1a56a2cb02e769c53e58eabcd2a472bf0500cbc2fd15e4cac290b21657484d178f9fb890f9e2d13cf11d76da5ff080c972ed07ecb27e6d56311d5efd60a1d8db90f7ae202de805fc080faec6ff0098bdb009666a1e1c0948b2af517de7368e627322c55ee4dc4758a40196e7a967e27eb62af1c95cadd5902f34ea502099bb48bf374e3df4418b319a3134c1dd86849a99e5fea13c02497e27ea425170bd84ce901e76fc9453d13cf74a2ca19207e6f843f519c504381e99a8fdceaa0cfec71a3a0e4aa45ae14e5d973e06021dea0d1146311850f36e7fe2c14c37d3e950e795ea82466e45d32df33f81bc6b7882c926bc71ee25c83358c4e040bd2aac398abe612a2decfd21197f957adc0048f70f7bb056eee38116702091dc7e3a0121dbd065ae6cb12123a8215f701623a682c2bf7c65b38493c601bafc677c9b0da06488070529ced262e435eb9f21b94434f2b9a7f59b676b373963eded3b873fcd056ca165e7417c049efdd236890e60b27978a6ee3af0eeb8ef06e4ad3d21e8749098c9ef3ea0a08d6c51c9a44dfcaa26b72b1985559f5070f92812e6f5e44193ce07202a4f702584e08cdc3c8107d0fa3fc120886267a6a8a96d52fa160ca0430fb3a09bb4879d0002590fa66bad4f8629244eb79450ab18ae39610e170553c84edcae0742515bf0000fe0aa9c1804752168f11d578f0fd99f3592518c705740fa2014265faf2692d3807d3dda53d2a8ef6265421d6e3711fb04a2dead0b950c221e576e651bbe93e40fb76ef87345685f9773fe5504d3b61738fea7d455c3a7dc20b4a80cd6cb74126711966b9abdd82881bee9cddb7b13efd2e1d28b2d66b85fd74bfa8658856bc0db077127b6f301b77c6f90d7cc14133edb9ca6d69291b76db6521ba0016f26a4fa37436d979b144ddcc670cd5efbb3c1b9e782cdf0832979f0258431983088e8cbeabf482b59877cfcddbed140d49bee852216a8147e69f82347931575d5f030897f09faaa2135c40597f888cd9d28bb61b10002372ab7e297504a5183f77e52376df7677104959e692732ab64959cb216d9d54b65889918386ccfebab0a96db33a2efbec9c25bb16e3d25b5ab40109bd33b409c585d4718dde8fa6ec275e5e69d4c65637842c96e704c026a6709bf9e7bd8ba898e584190dbeb69593a57f2e73e45dbabd4db6f5672705713f37cd8f593a29ff084ba2f466cd4266d41a6544490fa85dd9bb43e956ba02332515f28ea82916c0ff536dcaf65143ecffea61919e5d9d8f5bbd3afd59b41c95c4675778b0cd81b0145374d177bbc57cc364dae7a6d64123513fa3260b1b78b83f303225f5bb6179a456a2fc49a10e376d5dbeaebb71fc93fcc0c32f07f7ec37aa450c4ca823a89b7bca20d38924e7181d6ae2e17137b5297919c8bde7e9bef241cb35dafc3fc06a2ba5f2dea9367ee0c05b703a8f25a1cd067705f865f60aa8a426f7284dd5a1c96b659d2021f3919a5c8d8bf009871a8060d986b72633d289f6afb1f43111594b76f5e766e03d9fa0968f5b98121405ec054023b6ccbb3b2f18c5091aea7a9e7e38ae4746f861f14481f428bd14dbca926afe23ba37740e0e7b3b023979f4158aede5ddad4b889e5cbc786b21b2e0a9a2e950fc9b680a7b613b9e2259ea8fb8494baff99f50be1bc9590707e1be7ee07dcb39c933550b6535e5fe7d8ab25c40a909c17c3b994be33988a3f11f7e3895537685e7e25e4a64f069500350f81dfe52ae4391a7ab3e2493402e107635dfe95d2eeef4301c0c34ade886d8d6fe38fce39e2a9ac26ab78d3b8e7503c129d1208b1bc2a189a2258c07ad7045211e42acaa80c3bf2d9f8ddcc84cb17c4bff0776f43cbb2c22188fdbf3e482a489d04eaa7efb4eedf788db3a6a138363c7c82a9a2c32a1701d5ceb211890bcaf3a9887e9ac09b2e53533606f6e5346323aa835fe040af4a9dcb0a1f1e6ea3354024ca9a3cc10200a954ebfb53f180010a9553f22b135c76b3f4ba6ce6fb5ebb76dc1e42427fbbcf4abd750475a5591e28eb2969524db3ec2856ac5b3cea025b777c4ab02ebeb88c178e70c92ab4ef9b35bddeb9fa7062960112ab226e1de436b72973f2626a85fb8783a0cb5f12754434879214a9a45a88472254b7ff139ba7e392e7d10d8c05a900e36bd6777c654c888ab7ade6dca954448d1da094af544535c40caa49f113da363c0dc363f3412071e5f48778f3a084c537f42ae7df04b1790e8f8bfcfc7a522db40633962354a2583c8c59be70a3c095a144434f84712c8d77101550ae32f822958c27287e3657a380ddbb50dca12a74a7b90203c382402cab059409bb8afe60d0a2c3644b8763da3ee07ab5389a62a2ff78f017c608078d905eb01b651cc533f488b2586038c1a24a6e88752df2f2758f6781ec7751c5df5ad4524fa64ecc65269c9258a894711d0e69231ccf99e8977ccfb58b1e635ab1ec0bece7a2b5313d07b3e55374ad65cbd4dd9329c737e2093c10645083c91f738d8f0bb0b67899676096e2b3fb7286cf88f68c17fda4f207d827dbc2fd19e1f6898ab849a44db0014ce016bc3238f246694e9df9944595c70a9748ed3439e05b9809b3b676de03a8c048268398333a479f382749debcea7354a6cd61e1d41430fda455143df6cdd95017989c4a0e1ee0c36a35f069342978bc68fce8dd52827dd8905f69d494e65338154757cc8bbeb9596a2d5b37a4a4480408ac05443700025bee24891cc4657a778c0e1b4d545282b72a6d4f3fc64101e9baa118d629a8ca29ecb96075e24e9d581311f28c54f0863b7bee15612eacf4b0e6c2bd63e80f8b16f46c9d61a0d87132b4af9a38dedbc2ab08f8597c766add771d02195bea36467bfdc71fa5011fdeb9526b9475efb4f922841cf12773d02a1f4256342e8b97397676be5a44eace30e578c8ee0b92f7da790ec50a4b7cadc7f648b3b7207daa2605473d241c42fc982d03e47f9840846e25d3d94a81bc9fd222eb1863f2a341bc4324e9e45348410b66a60877dcf7e92ca2de415fb67b9fd59da8cacd449c8538acff802dd528c984de400d88558913f8cb30543d7f7e311e4cd395608db9eddee18e30a32e87469bbb74431daa5a531ccc6bce51a7a8600e8d3cb56880c1b4da4361b4546bc90e9e020599c9cb73da2e7d7e5ef3ea602830816f1990d8fdbd2c5eca0821c91c0f337e80c8fa4bc41ed6d14215b3d43b383baad5c84373695e0e4be3840876d83ecdee9f203ac8261f94db0b6631486105378ac594360ccaa48304fcc99614c2be7a7c1b6b1cdd48ff8ef3679ada56f9b83a2a1b5afd421e087390b34928d7408ac418cd6b22fbcc02461b96aba27bd1170b4abcf335c37b694ece6d12796aa49a8e390988b8b34dc9c9444fcc11fbab0ab8ada8df4fc44899c65aace426bbc3eee23bf1996be183c40577751def4e2f4a35bdc64dd3a9d4d22776ad90ccf6905913ac2b929b9ee2c6d09cf2a30163d49c448de4a075fb222cba98caccc7c9c2e822a5b53980085032aac8dd8dec9a715e5e7daf580fdcd0f6e192ba3c91b72d0f5a949e8ed58b9daa1781c17589e499ce92dc25ab4b30d35f55bfb527a4e55cd3c96827e077f2b112fe44a68391324485fc929a45987468c91141e28cd6010e45b1987b9888fcdc8dc9db340ef95b28ac085a2f2caa71702132526c0e28dce183e0d63499b33f4862249e90d41c2eb1f540802a85753f80b74bfd069186ba20c7ad38ae9faa658f41a91f9fd87bd21036b1523d1c810e24811ce689b4a5170dacdac7053dad80a3842e144d812255bb66b9dca875d77c8764de1b501c18d4d1c706600f7e12cc52512d25c42c7ee2ad7c2b4e9484ebe7324643bbb460d89bcc52bc87950de1639e33664608c88d07e69b751bd0c5d6a693138a228e5741758315258f66cf77cc49ea81dbbd05ef859e21acc8d5c55f569b571e4632fa13d68afcbed107167111e102989b3c8e5972336911c9df2b45506e1cb6da10d8c3c46c3799e6ebb8f55af039dfe29a82098abea90d6ffbd906ca74425ee10d00d58c5412c4f31debd1a82d0d915f5716d461660e191b51dd5d89a50ec6a374065173945b03daa992361a4c70c20d288f5b6661e61eb15ef7afd8e7fd6f1d6ff156952147f10c4ebe2ac1294e5236db9652437b4d7a934530869acce167a281a27cb9231f91f8efbefc7d2bc0a3d02cec64f95c823c508be8df982c5c5c6272c14e24947f98483b1ccdc2831bb130a8891169277a4144746da67cd2cd8755cde582786c869750528a93df31484a0a94515941e5402e1805c07cb4300e6c628e0cf42d1a3d0ce9f03bc98ed07e1f4ad9b00be25bc089838292f527f54da7af4c00fc4628681096ca659f77e10ff807aea54affc9e3a0e118e9b9dbf257fb2b2cac69836a0561b1a756826b308b0406b8b562c10696c118f72359ba789620c808c5b01b2fb79209dc14a31aa0d689f78d5c22f094b341d6c0b77443a94c53743824c9b6b06b9e2ce72326009b2d58b3c33c1530e7c294899c5d5baee55511656db9893c104538e8ef6482ef5b2823c6c172581f44656ec3a738181a28c404f20889a483e8ff1426fd77532bd28f160e9ad886fb421e700bb23a4f38089cca40c146fa1794b812639d0ea521e01f3234acfadf1eea2dc4d48b8a80aaa63290981975648f77f075d3cfbd30ebd2f7e7627aac82a0e2398560691424b3ae88536840c0db12a7933df53152b606bc71bf405b000a8e3ed782bc962ab9d98ab4b6a47ef79d9805ae1070b2e95f480ddb3dc7bc0a2f4f0c05b8602bb3db7a67a60debfc2864c73cccc91a0154dae7fd90f0fdc457d6ba31f3d11e32d43a1a922dfa5a16286e848e5437b4ad9caadb13ddf940c6322f9db80fde56862c9493c52de25fcdac2beae7dd47a75373751d61b18e8835b80936f65c72a7a82a49c1468d1bd9977f1c9c3f745702974a9b29f2059405b14f2960d70162d2e2f60200f7aa8e51dd1c9901c598b2f93dd3f3f9d17b32a583ac433c4f91bdf4656939850a93557e7093fe8d6791550e5b3492aeafb0b8b0f50c7983ccce5696ad61336656732359c8f8f0eb7ecf06823ee59776196a30bfe9d9d39e825f2336002c4b43650c94d4ab753ca220ec738c578d3b87eaad07eb72deed5258083d76975c62cbfe1663b1397ff2a88749b3fcb132abd9c7cd3398173cec7b0223eb74fcd3d151f1b31150b860869204bfdfa02db4a9758a16dee86e6b62a5b3723ca396ea723960660b90a7eb6020a42870275b75dbf96b9289867f738bc98a2ab8441f24e8676cab8dff2fdbcde06d9e2330b2c6904c67afc41dc7d1be43b5fa91f6fa900382630a3cfb33e3fd18924d73368a8fb2ccc59860b64c78c2f21572b053ac6335bc41cafb1f814d096ee7ddc7775856e4c78b749058f55d5f8c31ec3dcf0f0750db4196f64cf9fde13189deb16e823f7cc3ad369fba794bc861857a045dffab48660c679acda842cd0c45f2dde90be672e27ea095e1f11afb1b501d80acee83674c2c3013de345c8d6eece35dab2bd82e5774161c508beb34d2e3a4364ff889c6720b4c4aa221f1710b262b9ec1e036350e1d81d6ce5ad2e3a3f3025c670a86c060b665cb6e42b5541546b3494516580b8b2e36fe63899e5015540768ed4ff65a5c68fc4dc1d11e343cf56e71236c39eb0cee6d8dae6a01044385690ed9d4f27f6ca84c7b8cc6b7e74c6a41aa61f6bcad8a8b56bcbf03a561120883905013d045ba5023471ee04f4d79ed52c8d13adc0bed44602c64454e292cc4b187e5545d833b62f2667e3bc411c95184e07acee4fbb5b51222316b4919461267f70e8cc01def95aa2202d5d2d15c7590ed68d4a92904da82631c64113595f3bbe65de5c4edc79ffbd7cdf251c5d8570559030cb4d1836ade8edd887afb9dfa879388b4c2f03abfd50785861ed8f18f44b007062e91a02bfe10b0c5b4b4d6bdbb5a4f44204e23c6aaa7cb2a126a497cf0213bf5fee6e48de067ea28fdb6c888352c88afc8ea39e821b3c8e261c3722e39aa827505eea03819798002d6304909e6a507a61bd9490cc67f2f078c89c2a3b1658bc73dd19825f44d09fd6a147ea86961c73d9abdfca72222ed8a253e58458bdf051c84b5b1083ccf31cd62733a97b4ddb2cb3a53fca6cfaf4435a76d63b78541d0b52349f5a819be2d211e6b0c331622d5dad8448dfcc7f4f390f09365f647020b8be4b543de68377db5d0e4757b83e42c0c18a284c1396a9e9e0dc1bc471194b84fb25d0247b2b6a8d5a2924722612880bce38162b389b28a9039ca89b8e4f08439d64df67f9650914bd3a7b499c30bc079c655c5f1e492677ff27d08d6635dc3bd2692b0f496e8c755d6f56527b74175c271207c936f50808d71e6c33f8c54f103cdfbd83ad6c747202b24688a68c344305458b38a62bd11fe18ebe3326ffb879c3e582d7991eb286d71fc2041f8461faf473837b49c54aefbcf424628c2c62485c01f96cba31d7f08e37d4d03c930cc2f72ca566bc2e4cd386b13f505bc6c86b1dce95a3cdc5e730e51b1ad859e7bd011f280a0a116cfdcaf0156ecbf3d4b1600f00cab44b6d9c0a6b8576721a31a5df2ba6eeb8c0b6a1103342d6ce2eccee0041b668f381714917d600838d44d8eb9beb87cf9d81c8859e46af73f4a0b25d8147e3510b66b9f15261c0004c2f29d253005b4971f373de91fdcdbdb45907fea5734343109f25cf8c6992d8d80f65249b863718406c361a3f6251556076f24b05e7f54c7ed945e347ca2284ecf0238d735f216adb5e1051d444aade58d547363b61c5aecadf6db37c50737f7783e771a7b1ef570bdd71dc2893d8b7567087ea9ee996f8904c807ecfa1bb058b3504f4f6678763534534cf069712ddff2402bb1947f6dc53127a12d69d1953216d1711ae2491a930c708087d18eea9331ea4aee6a4e319b97c5ff71ac5e53a2e20d9cc5229172fd6b7bb218b5d6c95cb348422988aa549adb8382ae4b874824fb705ec2ae76e3736f6e6252a217454e7002c1409ea62ae4b8a02b18654c460678f44896bfb823a9b494720d26124ae9f667c692a916898d5a09ef423cc515f10e36e36962fb8583504d3c067793b16dee1ace4f60372c3026ab9b991b3d82aae5ec12a107cfbd8316ecf1d90da74abe59f573dd6fdb68fcb420246cb52633481f17c1480bafba9a2e1e8189cce76173fc87edfa90123870d99685ee9954ae965571ddf475d8bc7a777c885b698303bac69e38d5a07b24cb47961c59d02ffb25cee985b9b96e515cc9e8321720b1202cacccae2a0d7fa39d7013fc53d30de9a8c78375304c3a7e044a3cae2293d5cf6556c1c27ba5b56e31a6e814512f101f2824bd3d2e1593f95b5ac7f62577e38564ebd08b946c68bbadd62b8a9d926c53bd5517c46726c4a8bb92b32adfc2296dcbcb3c9623f2db46ca3115bea45c815595eee119fdd68b2c9634e35d49e91147bc562e63a992ab7130b37ebdba4d84e3b1acb688e8d6a11ee4296958fafc5ec56970d8f738aa1f78c5264472cf43f5a2f04073dd7a73484d657ea955dce02352e0db0315754e17633c2085391d95d420519b8ae13af9e0170e542fd2c6bdfd13f15318fab9c7a005a5121b81840c21c08b4a26f4e4e791c9c32f135dceaaefd51e2dd5b0ff5c55bead3ba9f17bb0cd0417442c2920255d8e90e535e89a1d1cce9ec15204330fbb0c09fbf08bef7c6a653f83eb345c126c4eea4bd4474cc5208d3f8e6adb236b5ce3b1e841b06c99a0cac43374b47695942212a0062c45486c7b82672530570c6fd6885d09900cb5c4f3b74d4bd88d4c4c75727bdd0d922a30c33679ecc72c33cd921abeb2cc6998bf6ee92a24c7809f985c1e7c1789b6385ee560cdb342aa7943ea65629be129bdd4c7ff0f541f365b9b924116de408d9d833541177726816fd67c3c152d8a037ac882d4322224f6a698f9e2f1ca3aa02a76d6a08452d752a0b49b9f50216934451d1a4c428b7ffe35374c8a8f3a871ae78544889af34a96c790b54386f0a7901e3e11ede824242df7bdf433339d39a599938330332cab6587337e06cb50dd0d840526986a0ee6d291d98580b4ebb28a52782a4d436b1ee908ffec162325e573f70fd3aa9d218efc135b39f41a1bf2393492d4e0e196e977bafcdd20f86b3b01341a1376ae112ed9b37c3534bb2124490cd615f7e31e3fe4cec4046f3c39176a288dd59775c4ba3d16546a7b44b00fbc36ee5e08c5df62dda2785129ff79b66fe9cf4f6779a6002c33ca7991bebc7780fdf117886e4ae2bc7de6bcae3e33c3aebde68ad77eb84f1986facc6f4e03f815dce5bc5a8b050d9e1a68b90c07e63f5ecac76d3ab82388aec3b9ce09a67b19aa6095385adc75226e826ac1c55b2ea6875432ea7c53c5e6cdffc9834594959499c2e389087289565d23ecfb0e157991acde3d03d9a37da48e1d18b11b5efddd655ec5a506b00876d13d4b46ce9a5234e84b662345fbb45a24438c32a6a35d8e126403b34d44d0f5ccc902f2f613c18d19d3fcfeb1cf0b5f70401e125efd77d96e6611abe6d6a2c5423565a34eb27520ef2d3eb2806b20c6f9d1c53fc29ba77c05a4d1d623346751ddaa1b193b66b4dad5a642223251265e9dbfb724829ab08de996dec44ce36a549f42f23c16cc05cc5fa883901ddeabec87bbfbb6197078ccd2c7cdee669ea6c0a65fe7cfad87063560c307181eb64533d0d0f1230269252d853c898918d9495c45957eb002e36dd95c25b4ea9ac0a23a103895fec5d63f14cacce989431b494fc813cf2d6b1528562ba61b7eef451c5d29f840c83bac1ab63092e6af46e53318ce4dbaba4b45f7f7f34fe116f32b4952ca3cc9956eb3518a314f90b8760b76e27ad9aeb215b4e006644016e81bea44aa652776198d4116b6f1b2d1ad504f2f9c44acb19d6e488a11ca3df0d353583eeb669f65b3085f73f2906f08229768259424cc4d39b3bc70684298ca65fcf9c480c7982816d2e53448a7d4e28a6cf51fc444f4e924aa1b89f31a89be2ed3909130fc6c5133638559ab7060c4428d1c79cd6eddc0f06aef04ac86180073553e4cb703301f02dec84795790c30043ec431dc84cb029cd3afa96631fd606bb2f1052d295d1236cc067d8bdfc73851334b1e193bb004b9fc446cec7307ea07982b657c2cc4d3e935a7b020040f18aaaffe049ae17565cd1776170015e2ca766815e435973067e5477ba857ae2a178fc5b95b3bae2fa499058c79f0280d491d5c65c8175d84bc882a7852f76352e5cdcf3eca7a49e06286f65f0a99c60633236bb1032298efbb473c88cc40ac5326516c756562cee112b7fb9d5d46661e3d9f2cdd904a6f0fe2dd7f4f5a0be93fa65cedf259814a834ad50b4beffeef8f5a7d2084d0da925eba7625ca3f45a99a86fbc085ea4c45829207e76484db67c9b23b5acc7b41c2d1333a003aa5ab682db84b779a0dc2a21685522af367928b271b735b3a0a45851fb4777e79b7d2544b308aa1b6110e23928ae0c9df0b0e1ea589db1e6f2b6a9eb07328423c6e916a407f09faa60ae5b4604c397f7a34e478c656b2f15d3ca8f16f568f35f796a0da1f93cb1d048eba4dea8850a610d762f62bef7628b637af9f11133afe0f50604a502eac1469a552092aca30e27531d57a22f9ca755a71a3fa5373c1ab1781cb6023183430dbe127dd2720ea5d1661133880b632ec81d2614a2aaaaaaaaaab26a76eb5cbd8891317f7de07c2c5e86b99e8f6c106e89dd5b4a29a594018b09bb093309412a600a1b632a429585a5449296ae8030b6a64049fb0dd8f71b1e5b8c559e74841999d27e033c4107540f305c25d420d37e03e6fb4dd4196153921d66968049fb4df00734b05ea0f122324496f69b2fdf6f9a567cbfb929cb95e572b9dcf7dcee7df5c2bc8cfb972316fff25c0f077578b16468c654d3500828690b1a66406a58390ac322edb9e3f7dc2f025c668cc6307162a629ed39201412242864568cf992f69c97ef391d418e5ab688a9e12199f69c0ca61bc9a1a42a3996d29eebf23d37e37b2e07058582a228fa1d2d16c3f411d547541f51b487832a142844b0b21435e5f8ca1fb888d6d0085231937694cb7794ab05928a8e10483990d28e02cfa490812a81c5222aede8d777540697314cc086b8c8f09276b4228f3ee3192ca92d31d28e6ef98e4a7d473baaa56cb4948d8d4df26dc874d0e62b1826b411da086d8c7fb94d0f072d40cd30cb6b18c2c797202b50a4302dc1828492761b2ddf6d42682903e6469023b2a6b4dbf440d224454719310949da6db27cb789faa0243a6c8cc9c2324249da6d2a1831c91032826aacc8d26e63fc6e6363a34fe0f4091c0e871b7b1c99c6699cc619ff725c0f07d5b118c9b329e3091f41d2cb7d044931868a080598761c96ef3812a8e05ad3450c93d798b4e37630719d18d321068856da7157bee3a0ba28799242e475949476dc90a3241f6088e9d13093769c95ef38216e88cb666ed9cced764bfe2df99593615e467d23f65bf12fbff5705005927471ed503a31054a7ac548991a464d39c94afbcdebfbedf747b88658b884c62869bf299141e3eacc1713664b69bf55f97e235342893146561126adb4df5280250c932416627480a5fd46e5fbed76b3725a39cff31cfb7b1a4fe3d94fe35f7ef670100a3dd230811a32a5a4489a83858e2b5543452dbea4fdf44df97efe40205545c71a2a4722aeb49fc0bf2076d524a3cb1527beb49fc5ef67d4df33529ec4c038fb5022ed67f0c945142167c41c3d88693fa57c3fcf13890dc9d95fdb57b71d6dda6db6b1b7916d5d1fcbbbadabdbbcfe725b0f07a15066bcc292a32c8c476a533484a38b1197de9ab4dba27cb735c084161e3e5670218195761bce8a48a5c711294d63d26e83f2ddd6a1c69895932089e3095fda6d342881048cca8b490b55da6d5ddf6d42dbb04351da6d5235a95aad561bfb5ab016acd58c7f79ad878376acb06cf1f05183094b0aa5cb16a2912f4d8e10497bcdf7e47bedb781202da0b09048583dd25ef341f2232baab1344244da6b4ebed7be6408410ab3660851da6b24e00266094192223624edb526df6bc2daf06fafe513b47c8246a3d1c69e06a40169401a8d684b8d981b6990c0d8a2036199124aa0a01021493b8df89d56b3101d61ecf05023a994765a2592700252048a0b092769a7717da74589780a6e7d2995750192765a7007474098192b7185448db4d3987ca7d168656699699ae6d89b649f7bd8a7f8d7f8979b3d1cd44185b1cb89a61553904ea41f00f3f5e58288d90a2e6937977c377f36f812e2820c19a11958da4d200edf4fcd2f8d9558dacdadefa619d73179c622c908156937c3c4444931030796a390b49b4abe9bddec2694124a5996c92f71b1c4c5121f4b7c2cf1b12c7b38a8c30a1a514c80f8918388043cb265896ac68f3522d25e26f95efe7a30a3298c992f204661d25e1ac1d802462152e29b4a7b89e47bd9f5c4c9d250d81a2867692f833090d10b5223481aa3a2b497c3ef6569f3bdc4203606b13732f91bfb7c6140e263a091e8fb1efefdbeb77070073f58b57f7f79f40062359645a44bda37f041d038419423e8e24590b4ef23df77d45fbe7b38080bb18fe558e8b3a2ad68ad937fb5511b35522335d2f897eb1e0e32614894af2ec4a91c6bd20c68843c6d99150d0d49bbf619f9ae7f465474955991f952f322ed3a0527b12e36ae6cd19a92765de4bb8e4a7aba61e662895794b4ebe00858d040f96531e3d124ed5aebbbd65ddf1ef9f6c83927ffe65b551536ccc41fe63bccc6bf3cf770503ddb3a41c4d453f1824754901a6544569e928ed29eb3bee7df0cae48414101f1dd487b069280468ba7a662902342d29eb1bee72823920d27a220c3324e69cf41266055925daea68850a53d577dcfc23cfccb7bde8282a1608cbcb8888bf8888ff888f18cef78f89777bcd50bf6aaa2ee0f03ffc7847f7f2f93cf82b816c4ed1799fc4bf6d7f7f5be302011782df05e1a97f7bb150c066fd58db2c20bbc4c7f3b3c3da152b3069906c8517aa98062642449511a2292f6ebfbcbbfdfdf02ea012cefb6ec17f096f76ebffe761bf637ed76ec2d991051a8286414fee575c7773bdc7db75b1c0812ab3e6a05d80768b44cc8b7b6878352b8bdbd1c08d501b77a00cb6f0b6e108a7d2cbf97ffa036a0fee529f0c0c82fe7000ee2f8d8733aebfadaa21524bd47a4b030e132a506559534027ff90e0775a0c8161a1b22a24640a597731ce4032652663c6071d185440a81cf45d922e283714b8987f472211cd4e1090a0a205b67ac82c6f472142ed003fe720a5ca0fd97eb78a07d33bde53b8883f256f02dcf950555e11ee6ce866739b6e903edbb8783f256deda5ed09eb7b6f02ddfc2adb587468a044578cd75deca5db827b4ebe25b8e9b819e55d0ae836ff949057744bb06bee5ba1868cfc9b75cfb7090166aed7bcb736668d7faf896ebe3050abb409a5bae9143344b0178cdb3500f6f8ca8d6639cc71863de8383506df363aea12201ae2080362b508552e3330f0bbda985f69c85f61c85f6dc23423b1e423b3ebee53843501c2168c7c0b73c841368bfc6b73c8425b45fe45b8e8302edb7ea2dc75942fb1dbee5651ab4dbe25b5e26d16e8f6fb93904edd6f7969b42d06e816fb94905c51942717a68e76f7bbf35585be345d2b09be88ef7fc3cdf6d9e8e5ab4c72a3109b37580b53ba63dc025c247be243123218d3a2186a106124cd020bcb6f23d751876085fb25c328d889b7753f38eee4adeb9c97b0f8df79f1aef425fe3e9423f264fef29793aaf9987f6ab15f52a32db48248f3f6637adf0789a00fa8cb712f7677bf7de7b6db8115ab89b28badb71def3f3ddbc3ed7844bc2d57031d89822ee0d5b65add0dfefc76443cf9c156afad0aa6448d505eaf7c6638ecd7be302f5cb74992e50d4fdc1419cef76f7de2b84ed0df21eb6aaa15724145a8fb7046a6faeb7f69faf7c779344f7775383bdf16c60cfaebd2d6ce793253ab2a58780a187af5b5aba8469e55202eb5db05c6729bd6023ad1cad0d98407dcb9373b501f56d449c336c8cd09e755b70c24210a2dd945216d19e755c760fdd1702487009d35abff2adac27c6d40625ad1cb50eb8386150cec55a8b7d6ec6dcda9c73b6d6561c64ab8fe53433f722e01bc466e7ab085f735f69b56ab840f7628c3131682d43d11c8aa27cc9f8288aa228e7684d51940b797894a720e304d2e772b95c8e77e0c6cbf1aba5303a0edc78e7066e3c34aa42ceb944ce7928f3e49c73ce3997f30839e79c73cec91eb2869cc2ea73ce3967aea127f39f0bb4c333372145f273ce9cc96bd0c03570e36930737272727278066ebc1c20891f61048e811b6f043397cbe572fc0229943c0fcf17a884e7e116b8f178501445511445519457e0c643c1fc0ecf3b9c0237de8e2984a24bde41903154b4b89071420c1525a5208c31218817f637811bef94c08d97732a7228250932434bcfd6a1a7c6c8af45952c42255b09dbb5d97a35d6a3cd6697804c6fc3b1d96c366bcbd96cbce2e2db5010bdadf6361f5e4990bcb86165cb172094cdc04223cb151763521b89b7ddb7d9780f0ebaa92db57121126fe329cc781bb75143de86a22a97cb71de8d97e328603c8aa228ca39dc78e8799ee7799ee779f20d37de89fbcf67a113669f7936334733df65141e3ef3f4c6cb7fe3d9a29ef37c81f8738ec28dc7cd2b132c3e43860c193264c89021438624b0b7fdbc8d47e0c6b3257d7dcec5aea933315230d1d125cd69c0147d30f3e2cbf128cd16e93387c08d971f70e36510fad1d1c1f03a1a5e8704067c374127023a9cc48bd7d1e126892bafc3d11d89d7e1bcc493d7e13d285e87fffcb4bc0e17d2e12824b1781d7ec28da7332ce1fb095f8b610389e26b088a0f62d25e853b601fd470950832744c7b1d1efdad5e43b55a0dc3f0bd8ed13e2431a13466051252ed670d91a01650491727353ba2766bb95aafd71baae59aad86abf1da6df06bbcd6acaf36fc5aaf9653fbc0e26bb50628e9d1f5bb5156bc91d69c7a9429ba4a93e5858cb466f4b59ab1c67f2ed0c96bdc04a1222a5fe32930f91ab751247e5fe315cce7e47007dc783911b8cacaca3e159f79cedcf455f9ccd1159ff94e4495cf9cbff8cc7b8cb47ce63f4f9fb9d0082e9f390a519f8fc27ce61c400a7ee60db8f17251c3883031d0284181b44122ed3d9fe31106f661e8834c9a343911df853ec76bd1e7722fc28c94892871e4534a49f05a61e2858d2d5f66a439dfe77c489fe30cb8f17210f87a8122f05d87e16d1ade4682096f3be16d5c6fe3d9c64da5b77194a8c9dbf8eef736ce4134791bef41f1b6ab10c5b771a122156fe342379e4d0305567850c14505910b3469ef7994571cf40264141a2d45b22288b4ff3c5af45de8515e713c7af3a8096321ae28e9623a2b93a2954b4c4b50ca27e4879814b5211ebdfa207c9407dd78e8f9a7ee4f0c7f6af893843ff955efc79f27377d7f72f4e43b0f5c7f72dee2cfab21b03f39d08d77fe2ce0c6ab89f13d060cdc841b0f03ce8d0b7a4d809c39b302c9c9cd51a907158b0f21cc8a3716c4cd4ded06dfdc9c373c288cec6f6e6e6e6e6e6e903737dc840b54fe0d1732f3376180020833e46b5c01375e6d270c93f4e7799e274fc08d77722c1b12aa0960208a8025093b0a1b7ee60800f3509520f97a04413245aa4a6aab65618a26924e50c9b295da88dec67f6e3c1b3fc08d773356e42f18e02f7003dc78174e780bc11c2c448420d264c402af38c80436c6a4825b452b230bf683050b3d0b350b16c642bc85db5b508115d507a3165056c0a4163afc082a41fa01e4ea8893d4821d7a0b3d38e8c782050b5c08c85b18531ac0d77809375ecd4422a642839890cf9c841b2fef0a70e359187b1144e004b8f14430c570f9dd8e9b3b8eeef86ec77b763b6ec26e27864702bec67d6ebc5ae6d9cc1ccd3d375e1ec08db713223e0078060017c08d0700f3ccea2bf07c81745f618b940d23f008b622adc0511c5402296493192e2348abb402df5d20ebe12b70122a70132e10ed2b4c69f224c3099f7d32e715be02af4716c89e3ef31dd9d967cecb889f798f59d367fe236486f5f94ce9330fc08d97871abec66beff91aaffde76bbc76a1af55ad4f8271ace22584d155daeb500449b61c21a550e34146da2bf16bbd1a5fa3e66b35d38cf18a058c11328ad29ad8123466825c4152a2495a3bfb1ae764435fe31a6ebc9a71841bafc29a1d1f02cf17a8011f02e7b9f14238bf9f90832446267a2aba1431e9be6c99e2c44a19325360e8cc74489daed4e96c3a5ee68b16099f4df8cc5fbc2e84d7e9743ace7190edf5e874ba354faf43e3e3b3166152459a1169c63ef39e1f35559ff9ce8d9785d0f0c82ce00293021e512555a5b622205a84114b620a7a496d656fe3fcc6b371b131355f4b6246eb8c8faff10c375e8debdc783a64191d8fc3330ecfb9f170fa095ff2da6bafc32fc9be167b255ada12a7e45b44655abc52641996485a3829c26508448e22e59ab23ccb325796f6c6971e3c8819c204aa2999761b548111b0a2154d4b5bcea4dd56a92979b7c36eb7be1c4abb257e597a219bbeb4f9f28c5896a7e391323f88a465c5b294e5248c1031adafb44cf325e71a843f2517426e7dc953d0f1555846c697bc2ef91b9e6f38861befc6acb572249ff9851b2f57a196cff19ce3166ebc9c49e5cf939b38a8c21a0f413557b6989ad2b303902835b8c8289959194bcfe49f5c841bef44ee7894e70b64e151bebbf150b3a6fb1ac7f0355ea1be76e16b358e627d0df9fb1a07c08d57e3635ebced6ddcc4411d60a4194b4162e8f3c24a6dc821dfd516105c5623521b9ab7f10a3c841baf34be507a1b9e6db8eec6b3c1c155b233a7e21017e28a329c0a18417a8cf8a10209ee0c0e57c30971199771bcd7b102583153752655c6c9151c8db0164d4c47379ad26e7b66be5bdfef713fbd5eaf872b7118c77fbeebbe6471e471e6e3b6ac9182820a0489262a29ce8293940d1442a8aa34a5b8b1c7e170368ec3b940f1b817438f7341e3cbb274e1e44b8e73e395bbacfb8ce133af5c3e9f399afcdce2c7677e73e3655eabd56a359ebbf16a2c9cde36c2db387ae3d96c6e3cdc8ae2df78be71dc8d773be169546a727021f322a45292f62a348012a7135b4a84bd204a7b15fb5e89c5a7f5ea558d4fa359daf73af6b4154352b0cc9e08b1a4bd260370a30bcb8d23135daca4ddf6cad02ccd2e3d8d57dc2df06934eba4e469e5d32a92201e8e1e9cb4521ca5348ee2201e29ac2821545e5098a434324fe31cd368342e04f6b4ab15c3a7f1e49f3c9f62a4bedc5f1e804ac9f74362920296963f2469b4c6969ca032252d915ff2f3c62b9dbccdc66d379ecd24a3f5f97792465f26944c505993668ee2a00e43b0acb4964a5c899222cd6332bf5201f5350d5fe3b41baf76e3d16cd48d879976f451ef949c0242af35e6d91a797bece6d16152e81682b385e0e0e0e0e07cc7d9429c8de3f597e3f4705009468881d2e54b56931d4c6894a81cd162a5044c3b8e98ef38bf619a1c6329caa4acbea41de78c070f24468a2a9892d28e13e63b0ed21a291c411659c686a41da74271058c18495e2089483b4ed8771c1ca3b4e36c1737dbc5cdcd4df2efcd66bae937615ec11be2df9be25f7ed3c301fa93246934cdcdde921ed59852731389685ff5109e1f8ce769fc8a65a567d45a6b9d416bad754e6badb58e58bdd65adb2073e2bdf7de7a46d6580a53eb8954069f45eae85c8bfc8ab524cc2209a0a6e6636f34014e6a8449489870e2949655aaa6c03cb4b0b1a555630910da0d2213a3e0eb0354962d2e642c468021a346f20b16910c1a57886cb0ae286980e14be2d584f894650436b8ce98a414612a8afa92c3d2b1952bcbd28af14b8e0197537befbd15f07befbda386d9dcfb52c91b8c8e556bad9750b1345a1a1f671a2abba40287a2aaaba5589665599e25edcbb22ccb92c7bc3db65e4df9fa12d53bbab22ccbb22c4b1d5a310eda2065cb05d512a624607a7366468c06892543a2a0c88041c9a829b359d1929b2547cb25545f720c3ad07ca94bcb5287a23ac9e40075d4dcd449463ca6431118114715d4892e60d27d82324684f0a0b2926422dd4f26d79a6637f890349a79de1e3255b49617de7befad35568d7c45262a7766532f9035b3ad6a8ed8a77ebed25a9fe87eadb5d6bac5f5a94ee60d746a67c5d3115ad7a77eef41e1665e3537678cc8573b4a64e841738d47b7f31a1245489f0b32145752cd4d1c9442e1f2c295f4e50c52aa9d5e63b548bea6bdc672e1e1b5d6fb6604adbaace39909d7f9b837b59b5b7d2abf5af7cb75dd391c7a66b1e18f79c963c3fcf10532bc0c70c9d3cdaa79faaef2f4cbbb1c63de2fc7381dd4ed06ec3cca35deefbdf7da5ac2e5e997e7db13428e6c964725ced50ec185a95bdc7befc5f970efbdd766db9b0e9bedf838b823ecbc0d9f76a71aa9576fd3d7eeaa715a6fcba7dd39b5a03dbfad5fde56d65dcb8ccb3cdd067f79722da166795b792100b584ca79be62bd8d66de9aad1927c20cb4e79aad86530f01dee76db596b85c4b28917e8f837bc102f906fa67d0f338443a639b6b2b8aabb584ca11f01daecdbd282a465e4c8c276c150855fccbf14dc7064afbbd26098cd8d72a813d0209982c5f6bcffe6ac3517ee596eb1b24fd955b5e3d50c57614c37419a1034b4b6ab3a9e54dc7066ab9cdc4a679cd6d66d3b4b534b50daeffe02fef3141ed572158efebedb10136ebd9b31468afc81368af5f26d05e8b4a28d0bb54c5fc7a0505e2bb690109e56286bdd0def3f549cd77f3eacc5ac245bdecaf574f63dfcd37ad036eefd77ccff85644ed677e7bd031c46a85289ce8a9d39d5b879e3b4e01144e1d069caf574f59be6b20d6b019eb362f5c26a83943833d11b199ad6ae898e931b711dd1edcbb3d9827c7c26e0fbe471c2ab45b2226ba41760af7acacbe5ba2d6e36e895b8fbb250689412e62b18b8899b66ebef75a6b5388410becd4e720b1894824a2403eeee23840d06eabba6e906d717d8817c87227277be3c6775b0534057c6c9ba097dbaadb837b825ae1edc13cf358abdb83792d047a91f777abc5a7055ae00c6cd56b9e354fbfafad0dc2f06d10b4de96b7ebb30decbd41185a277406b60a67ecbcd7daaac7f9e2eb04b57848747b9263f76804ed167889ee05c21c0d42cd9ddbbb3df71efddd47b4a375e72e402478b9e6a9c9402d50f36ed6cb7950f2f4dd6ef3a09a27c9ff974de49c42af618fad4f1744675093e75bcdad4efee57683412dc74bb69c824dfee5e811f47264c53edd123ff30007b7c7d6dbd3eb158ddee3e07ebe7707e760a097a34b1768bb010ecc5b9e99f5def2dc4441bb696fc5f73b5c3b01dbb05afbb4f496638003d556de31bce517f0e51cc0f8be2d61ecf57a1e8288aed1061199c82b19c1fc96a31004cabd9dc1f49683e0ee5c40e8d5b57381214520abe2ad59b1bd35e334d35b9c2cd4562641f9731c13a80daa4fe5358de9e1ab15f195f7dddb5a42f580f6faf5959b3d542786ee2ab45a8eb9e53f3848062498b010bb42e645040563c0a2bcccb8b1650bbf9c7fe7ff28dc1f1c54a904498e8852e209184c6d0cacb1e25418283243a905b03126c07c68114524b54455498a3e1d5d5552cb7564d09b29688906fd29a2dd267dbd1747c141b4f68bb18554a22cc9a745c22ac70e2c265361e0951abb69afc84c5844261ef5961ccc67f40a2d17936125e80d1109d444a17f68f9e29e39c1e87400a84142d7ac5d20288132f09d0e430f0e18b83b3f18763adea3a57bcb31c041874143f9f52aeacc7712b00ea419659ac3e267f3e7f34e68670e8d6f0a09716415be697c73f799dba837994cd3bead1200157af37b6ffe26bee5964b75803ed603585e35182f5f39092e9005d62861b89a94294a2ef1a8c7324b76a861a2f5e20a904d710218c79e871250482ad2ca7f701009a0c020cba448793224adbc87efc86c72ec02e5bea2f89ac443c5182be314a319692dd3fad0e5050c2d3fd2caed576d4096c1e65ec50b5403a1b7277050871a2f5c28a9b91a527326addc0a7150e9236a89d25959104b5aedd2576e83382800655e8a9ab6aac09cd2ca6d140e5ac091151f4ca448aa004bab05566e7f955bdf05b24a5f3be4a082e5c98b6b4588b3b4721ee0a00e3e889e66803d390693d2ca53a83c0695afa0727bb46117e872ddd7da7b1ef71f135e3cf92ee44bfb098fab160eef95d82586f4bd1ea5bd7a3dae1559c7d6847daf24d25ec91e5b22dfe36e7fbda6efd6b6d8f16e9da244e0dd06a7c6207db7554969b7561678b75ac30bbc5b62d798d8776bbd1e7f1df1d8632ee3bb4d8e48bb257b7c7ffdfa74f0521295ef17f8b7c563deafd38d7accef9489e1f75b5522edd7ea31ef576b7889289a7cbfc51369bf5e8fefd73d5ea40a12df6f5245da2fd9634c8473601469c72d1e63a7a8e087efb8eac7ea31d6c269c743e2d216eff57a4b69c75e8ff1d74d3b3e62a4120fac94764cf638133dc65a706ef198639ecdfb21c598eb764c413f278afc4eceefececf0ddcecece0e4fc18db72374212727a77e7d4ecdb1393c39391ccce7e4e4e4e4e4e4e4e47021e0e728113d8fee79b2ca54c148e6889194f2701407e5a89c9485106226e5ccc4f37014dc783cdc8494dfd9d9d9e1bb1d7e821b6fc704375e0e9acbe572b99acb55c8e572b9dc4e2e975b4af1b95c2e97cbe5f05c2ec7859e3ec753a0fa1c2fc18db743821b2f6704375e2eaa769ee779cfdc79cf133dcff33c79cef33ccff3bc67ce79cf93e7e4e4e470a52ddbc9355ca01d7edaf43cb910953f790a5f7f721b35f6674e4e4e4e4e0e17c18d97c3c3c3c3c3773c3c043cc76b522e97cbe594a47c8e83e0c6cb2919f99d1d5e08134d8ac8ae134fe90e99deb0884cd98d1f52d21d9bf43bfc0337de8e076ebc13b5286a51f486f28af21abaddde5459a7c36d6e62f101a2c6dd65a54d9342ac344c3593839421469a65708a89c44f1d4b22b622c44525b5b67beb29bf5a2ccbf2282513c22899be086f5e54c0dce666f552c4c82f039ab434b3e8ccda746199aa76926d47b03695a4a53d90499bd7365a6b9dfc8a15f57bad93387aaff3de7befac13c13c35d289348dc6e46fefbd37ae87a2d5f85a6bad6b16981baf79d53d2736312b59e30d484c17e7d0bd6287b1d65a9f1d5d9fbac4e1ac45940a3698f458b056c43ed5dcb5d3b4f69200a23a5c59966589966559963aa83ed241fa420a431a3ba2b12e46d67164a29b9b5b7ebf77b82c4ff8b22ccb728ad7f5a95f7ac39430adcb32f96559d66a54461a97258b2f4b5c83370dcf2dc05acaa5cabed856b1d4bb2ccb928a10fb94c12c125b6b13b572d4ed743a0c794cc308e288e208230e9ace868589b6e598d32cc5107c18fa91b54474c1e584195e928ed054a1185204c38664d48ed2c36d6e5a1136299188e0ba18c91efd5b5f2e5bd7a776995ee8d78e039730de4384755ae94b912e5f0d20c4f0fa54222f443188710c6b74620b9a1d582c828811c017191744cc1489e1c26a25a9dc7838fc84b96aefbdf7beb22afede40729941e948c20bdfdeb4cdcd4adc41054d6d06d2b7c378efbdb518af4f3d72d082dc7befadcb46ad5596a12ccbb22c735e5c4c00cdc69aa0484b23346b8068753585009296595f62261b8d7634816934b0a62355d45c9d39998939121a7d15873c4061ad28b18e450c689eb2f7de7b93e95d9feafb01c9307132513d3f250e9f16391ac4a41dc65bc5f0fa54224f45d1e754e326cb1fab2a0106d0b7e380d4a3e1d6f8ae4ffd715803a4edccb2488c311cbcf7d65ae3183b6badf50fa7eb536f9853e8ffb0da7113ca6bafb22ccb526b2d2dad2438689b9b5c59471a422caa76d2ee476bad9dae4f9dda61a9bca7e4898544c90ea62c455abeba924061014547083320aeb0d098c945a07a3bb896297d28722471a564e5c78daf1c5e452957455648e30b93f368b36fb8f7de7befbd7718322c19a6b7fbd1334201c3498ca117bea6a2d8941e2bb21021d3636391097256319c123e5baf5c410206d9438d9495215e46e38734421f3786d0541cf70b230deb18c4eb538b690ce3ce6c639d0c1d1d323d5f11bc45541cc2bdf7de196e29824978a73b8c7b0a12784760396a5d49e2aa1159a29e30dd8aaefc84ec28752979297d31fddef828b8b939b637378f606121007e34450c3c4ba4648fb385fe61b6ef13d187f7de7b6ba413237190e0508b47d6ed96bfbdf746eebdf73eb7d408ab315683cc06918d231b4bfb87669e10e28a083245b6381d530d2544d6170e2f31689208d1f142d7cab22c4bad7518e9b86c5d44d2fc8eda99a5c6345c8578d1847cd18484ed866c1bc75204665d761c35a9284ea59baccad18809952866acd5958de46902075ff077f3fb66efbd7f5fb1805abff7ef8186247e39bebe8650e91e31a568cc189029e45590ae1653327cd111499a6d0c06695a6b7d54619f6a6a2df9bdb1050e20920338d1d3387299f200468510028efa8ae564c9d4a6940f9a10038e61532f3f2f43134f394b291f3421062c2f44da8bcdcd32d42cf5a55d29afeb535feacb4477e0d08dd1b0081eb6ae4fede279f00a5201c7b80103e203b9755996655215f6a96689436ca9d492312e7be56611000e4a555689d08680ba82fcaab1e6681b4da521730388102740b68cb7ad30a7132c30aab0f0600cd1d561c514b032e08e345d467e18e196006f3861618a375dc32480a1cdefe8fad425deaf85d30a74731881ac62288d64ad86c7eccd4d936f7ea2babdf7de7b73555d9f2a1c128bc692eb787d6a2944d339d3344d31c8332da46841b164f525060ca6145886eca83a5233729665a9437597f209997461c39adede7befbdc18ad7a71acd14ec88ee72893dec10480270f0f0ab43d6108553447419fae830c5058615939a2b338a3e6ebe5c29e1297b46ec83c92be39300337eda6766e9613189b59ad65a13bf5e5541bed644da8f9bd65a17c298ae315e63be745213d2ce6b1ace43c7eb5391bca1a40c239ccde6a6161f65eae6e603846461801a6189ec04d189a3bd659861adb5d65a8b855d9f3ac6418cccc66b8d691b400cf7de7b9b5f2955151e11b4a6d5d00a2d67c25c5c19daf0a40856922fbea20caa1ab47c84c43ed544f736755a6bad75caa2a37bcb1a626e19136843f258dbdc6c42044804b4d65a6b9d948269b4a4171a93108a803922c58d6b55d45599b344bf8a8c61a0716e6e5629c214b5cdcd273c9a94a121806370ca066ecace22fa19cd8cec9c562295b4358414a6cb121c2f92b0a02449a9d243849a12cbccc72dcbb22c757046c9ca010cd346ca1bd0082d58953612141146038ced2b7f51b40dbf208de67332e1c30595950da81f5e52768011fb00a46cca85989b95f443f5930862ce12d1a593398445f1660c7718eb63d8f5a9c731f34886eef6de7beb3edccd4d218e1c626ed50006606fd5572c29c85b5596655966c879efbdbf7ed7a702bf98d0afa81ddf7b6b7d41fabd7349150245b04a60c839671bc2b6b959a31499cafa1d767fabcaf481e41c8c7e1589c3ddaae563ac2cb5ba42e4cab22c4b5c9665596628b1f7de7b6f26a2eb538f784c4b750a1532c32f6a8aa884d8e0c915915295271e5f78346dd91469aa52c2e5f0e5001bcc8cf77904d17bad10990a93e8d5569ad75aeb94ca21cd344badb5d6b5bdf7de5a9f44b7d4e8dbe1bd09f07befbdcbc8ae4f25e2607694cdbd37181ec2c6134e30b1f16bd692236cf41de121a58935a439a254c48a882ba4ac1d6367598e4c0181b24387a632cbb23c3755599617be2ccbb24421d4ba4cc2279a9175cc197288cb8b27a32812c486983525b160e2a5c3b6c14c54e54531d92a00fc06c0de7b577dcd3a22fcbdab3698e3ed662bd692f19801a31bca7ed9a99884752c7a2c7c2c7e38646ce9f2deb8df7bef1dc4877daab9f7de1cc3c905ef6d9a1c4c24f7de7befcdcdba9515010d1c7ebdaad3e5885995881f39c8a4bb0888d4142945ce9809d24cf47e599665590e69a1d3214e5ad7dcc1471a2d5d44a6806650a46598d81326452b1c5723725c99dd60fdf62e7d2eccb68ea5da15f29e5828ce2dd513c4418c11412a314a10013bf0205343e5414a0b88190e2a2dd92a31923263436a78434bba6b9229620c1869cc8a78a45bcac60e633d757daa95c973a185fa8a3035c139c9b2710c1d1dacd75a6b9de5457cad7592d818ce2a83e634d25aeb1cc6eb538f2612fd1cc91d2fb5d63ac65e6b9d0ebd81acc873de7bef3d64ecfad4216426d1f5a13d91a3ddde9baacbef5de21fcdd476db204b038851ddedc91283d21addd9405e1407552d1bc9dad65af39cf30d26ec537fa3bc2c4b1868c949f8c936865aeff82e52d14c0d87ca80208285d4e3c444118f31578648378a48d1b81065c3616898ab5d8a5cddc84083169795a824c95bea333989103475030000288318000806512087b230cec2640f14800f579c54583c2c91c7e3112908a3208681100641100000000280e10080c131e6157702b763f8cd8dbafe5844d47a92be5b2d96530fc7460ed773251e7e719337509d14cc473ee97d5642d2c9a49e79142851a0f1908952bb842752d41c4f89404a1c4a3b50fcd0912e03c00fcfa81383892a717e5e5d84489dc099294ff6c7391029e1e2cb719439d4a8b90882968ab71b0470a7468eb6d2a88d893572a5923f2c11c23bdc63297ec17e28a14b2f6af4b1c92ddcab4e9500e1ee6a13b7b36ecf2b49949e6ca3a95f46f7ff716ec5bb75c0d5cb69175d47e0eb5868067cc3b9f9dc3cd06be175cf4e61a7eed4eef84c97ea81ce71112d5ed479f112d1bf7a284a4d24fa9f9e80b2b9acf7d9b936ad0ef88e5905430cea0139f47ef366f4f240353d819e9706f5f9fc2ac4d291f251d69786b58123dd4842197172a16cffa2e5d10b596360395992340ca03cc12683b17f06a8cc3e8de1e98a3dce0fc9f994e20b76dc40b86194831411359e6538f9c07a5788003c79c98e496a591036e93a1e4a1307ab580a9402d2e22cb568b868a8bc0c1fe9fae5fa875bdf65f13df4b2a1f384c6937022fec8b552b8c96adf1c0c5c0290930454713c068a87f3c2b42eb6453aeb054ec9b096645e03ef82614a46b5a4eaa6df18b266c15212303b6e45f3a46039d8d1466abea4fc62f6af11e02dacbd60786fbaeae720b7e845f16800effda009259a316dd9908d5e9655ae78cea8234463ae336d80764b97e6bb625de8cdfb52d9bbaacc4554320c784c39374d995849337ff64068926a7b4e20c9000f714a7a46922914fd8fb11bdee6139e9bb5012bc9f5c9aef5aa53d55cffe1d86715bc544eca37657b9d6a51a5665fc200d6912e0a41a56d5c222788a8254712d67a8d291a094297100306e7fa9144ba725debc704b7e76046410e4b95901b64a696a3fbf410de27c7a14c09cda36a8aeb1097af40800a390d1076f8e1d62d7b821a2c0b2f72b00fb9aa90bb539b726b1bf67254ac1e19027b2c7da798364e9a555e4a02ec70f1240f61f85dc7abf9182d2b612d0bba33b9ab586ca4a942f15f5811828d5da9ec2a0b945c0f1b6ba5b2aab03a50ed9d1475eb9b41b3d1fbe3359bfcf3b5bade5b5c0a74021822d61570efbc777cfbb94b9fdc35141757af750954567827742f01b06e04e2a52d3a1274246bfd35be0f31d9c3eb08ff67dc3e6aa01ebce1f9f944ece2a3500642eb873db5ff8d99e3271ffafcc9a3118089600f0d045fda17c255700cb7b759e8ddad6814970ccbb5c653bc48814d8ebbb11c7d0a7b44ab584069636a0c284f6ea49c0a94d643b8341a6dc74cf676041758437d011039a925878aa360e227cb5c2ca72c835b24a7a2710ae1683af97e9a1034775ac5268546eeca861366f1ffb0957f6736dc2c669053f5d799f13a4c5146b24e09f5db8f18c46262082564311a84fa478fe948d2c820e0566160c09f60952965f2d132e08546ab3d54b7ea942b3785a0fc224f6965cb9024521e3792ed29207544c1e50dbe00a6048af649f8753682ef05a47c048d2078033cc9ab3da78e914c47641aad8509427bec29055f9135fd2d54258a4c93b49b495199ceb23e0a4682a69712707a07a5255734df63422a00d7e5a9985e4ba8172e7c26d7f45c90cb74fb5f406a9ca82b9a62b5de306bc62154eb10d651e9faa0825f0893c3f93f54c2d2481e2cf809bac52b2858ae9356d38e3b9005a5319d03579c98e5cd123848e3f0f4e410cf3a68d80f9692f58006454fd2071097035897acaae6176edac498d44abe0903023ce6353a4f39e8fbe02f3c79f209ddb5ee511869821f0e23730b6b82c963943e9b07263daf14a219729526fe32c1bdc2f4f1cf533ee304b9e2915fa6b9aec8b8304b1a859c3279885e4f2bbb3a4926822ccfc6791021bd9df10d7c48c4777bc3dc901cc7cf4d3a5e5ca0b842b8d2a2c1a42b39826873c2694497f2f2106fa5e8820fb1de09ecca219c616b68ef3d710b4713d6b373da53621d6a2d869d3d516c024eb45b7184688b6c37384c7596c833587b97ed488062820db598511df12ac45864f56ef8b1f0f9a3ab40933220ec5148fc641676a3463b31f5b4d0f1c18068c1acd659d84b0232dbf78e2f12b05dcf415aaffd3020a412577ca21f68068978290d913da54125a62f00028346a13f53d67a3c8fc93459e024907993d1424a04624f6f7f5a8149190f96918b1c855ec09dfd04d0235c60b8b6071af815d02f0ed9062d93307a2d9b69ed01e12d1ae01424f635d91b993e62522ad9566d892afc986c0a7499a7534128f18b676bc12d9f4e4b2617f9f6d25abecb2f0ade437f062806a4128e641b68634a381ea2f37463e76d081dfd884b06e4b855c18f7f7f514d7baab93c4144d23d58a24acf3a0c4d82c59f911f571fdc0c9de4b98e12a4bf976416a2dfd30a9d2b2894f8f5180c6a6a6ab793f5f62b380214eb538113464f16de4c4c72088f2dd6af9766e084f57c38ca20b8e0508c43322a5e67e0ce38f27c8091ba931495d57287c9c200f5ed24a8491da040e806666b72224652a73b092988bc72f8d6e192f2aa2ec6cb6713c9c40033deb937f004a7018c4e7b3c59b63b4eb21f0acc813fc1867b701c01ec14d67b226d810b051f4adc6f2d6e9618ed567de685aecb13511098fe7f5ab42780cc5939d7c0037486e05952061518d3f710d34fe81192d18d4d88f874d893cd0446a3304826e1a564b21f31bc0c505f80ce9ae2a260098c332ee63878af436dd7db12c30b527474c20915703d1a3203b23d750554ee26cbfba3fb33442bb1c1f4ffe4aab0abe9f80b20cf07e5f15861113f48a20f70dc1ca4b4e588472db853c5a4467ee0b51ac4e6c9f6e0858b6f79c0f7cbf0bed09dbc4a2fc8644b2e297868636ab00454e3eff64b75d87df04d1e470f299bfdd3a138203cf01b95867821a0566821db93aef437e554714b684ccf7039b9f21c5cfdb7c92960850c88be6ef069128494d656a8fccd6cd6324f63cb4ea5c74088a937c485b4defc2af1f859c7301a9fd7bd21789ee9078aba063b75d9b5cdb3bb37136850f089837852b7e2d6e7e75aec04d454534431973aec84faab1c4a46930a340ca6484de4808270241a6ed74ebb3e58e64fca5c8c2207d23ebe46eca844bccd087c1e6d71cbc39e45870c5eb4d552f2d0fbb9bdbb1668ed18598067fe9ab1b1fe891f0f18dfb5d2106370a414f7e646f07ca7346e4a780b9d2a5c089ecda95b228e49ef39e6572abe94e87b0c2d0052194b83dfbd34ab0a3a5f3c8ee99a89192724ce265e1714b26715c37bbdd89c1b9b318c1e005b69aa9e3a337c4776a836c2849d081af76d279a8c8afadd85d97d0408a071f90c21092f89e83dd8fff0883c46f634202dddb21822709eba3b1f187bd1681c23bc11b6f492ee4b373c9127442224f1cc1d9aab782adc146bc41c1cf25c994c14bca38dc751e2beccbb1ff6904cbaf46d8c6c42e432e6e1ffb96f397db49a98101963c80a928f3d342652c4f9a7a93188a623698f807f072642b78fd43240e8d7563d715777341fc017e4ca01b317da380be273d0fd02b6119629919f8fffa466afacdc3ba43fa7b774bfca95ae9a6106bd454951685cd0a98207d23e44f97e1dc10e800992646f6f161c4aa9a7501185860d09a9141ba45e33caa539343e1393ac1c8b0baadb8d6e058e307a119c39264dadfce7b78b724b13efc07b39745a4a94ccd45cd42441639f34156c4104a98f23116e723572e0d79c4470ea7c551476ba326e5174fe312b746bbb4187a861600a9ef0400432843c7c42041e74e89e978be9254681d70aca0bb1560ac09b50e906f125fafe9e89b9b6435b582d28159112d4769e9f783b8baab13b46ed0ac61ff76217e0c7d8d287c5d5154f0c149f8959249ae7fa64d9e2b63994dfac28871b60e0beba511c056d732986b58150dc8137c40ef1828bbbe4e26145c86d27fa8f2e51a741ce4e4baf572a9db645a8010247dd33f3802d957b7581ae1e1eea4ea5c257229134ae7cbc294b11642e68eedf59f1dcafa58b81bcee25c8dda2e9133437b69976df30fade0a9943f10cb1997dd396278d35edf797b4efa464fe96d76fb3d468ed5cb60591dda0a26b7f64394d2ebbee03c8b9b9820bd76e4116760b5ae1cf814e6cc79f1830251d28c81c04860193a7dd2130b6a518b4d482ebf9797a39fe8b75ac3e10389c7ea165b409ac7746c849a002d67e52e493f977b566dde5fd5d260cad5a08c89d205e056040a1b20579fb2e25aab2d18432c0fb77d4b3c361e41d45f818ab58865f694876301efe450ae73daf22c5ab5961e2c8bc3c9608b75a4ab182459ce2ddbce7458d005206869b1e0ecd4fc7c297d66061d6be0b6d9617b3a4ab8b58d5982ec59621268a4a4bb79c1daa8d7d9b75a22ffb4146258746f7928286e0943429c27d91219cdac0f6bc7e130a7e04220e60915e2a5ffa14d81da348bbb3463095f27be6ecfb2fc5c2b3a3244309bd96f184c27024de3fad80dc620a3c30a923306f8871d044e441cfbcfc33cb3d0acbb9b1c70f3565ba0ce582020ef5f61ad0429c056c228e278b096bc9882863e9073822c55cb78d6d16a5c6d03e4ca3cb862115950da50489bc8aa0639da04f175e08c7e2a92a06277a14d9661f9faa4fb834b229b6a41055ec2769c411949e2fb434a2dadc9679733db26cf5ac9055fa249af0585381b08372bb1e89bb1d23f4080787545c85433f51839932a6c207f09946ee719fab58b86891017fbf020aa7d34ec17249c4e93842fe17fdc023d98f754c53237b9cc2354287d869b945e35d82c65459461bf27c7a52ba1c8f037c2a6556805714b3846c4415456e21c3424fa55afad40b5ceb575d132fcea75390b3225faf790d7f84bebe25196e3d382400b25d3e72bbe963b3d92ef297325dd70fff29db9290cb03edf590c4896ab4ec879a7aab845d794363f199b990591437866c44ff45d1d75c57cb66748af5d5fc052a2dfb75ca537edf8e163b76e728514e029df02c8cdcd22bd9cb1f2678e11bdc1921ff452dc794b743102e5252b525e2a0584a04b631c51f5cb69a2d258fcaec072783ce2eced86017958f13df5a3eb4a5878d9a4999797db1ab046ab2b44335210880869f740913d5ace23f0f9ce97e2960ae1d6d1245ca5ff428e6d7be522bffd4499741e8d939c6c03e94446f6914fcd82c2b3fa80e0959655ffe2c90d6f0440605f21c7d2a9c892581ac0afe46ec99a1a9c25fc8af7eb62f151356f54c3c99c103e0db27b7f19855bb0f7560981a9be2450d2171e6791caabc7322f992ebe96fb7950f946c57d94c82515e38d4c36f172021867005c1e88524f3ecc8238013e52f39b155042cd20218c515bb42c2588cb72d9b8d6fc5510dd9700033fece8498c7e083de2332c6a44a1350e0d57215da81a785ae51d0d3215ddeb7bcea8214db5874e6798cba548a4fc8a1cdd392ba0dd1d9fc203a1a28e76afa8e9487dda4391d9cb2be4ed862af816c626af3113426537591abbf0140bb7edeeb6489d37ef49a5b9f45709af196cc0d85cf02beb3538a795392c14e781d74293d781eff96ac9b14531d819b04469819b6096fd745924b41edef9da6b1e9a9d5a12cfb22e1558275c2b881e286542c312f5a1c38800f3559f56b6ec2ac6a1a285581518ae5c1a20b61c2c7cd9f904aa4fd5d136ed89314ccb137062f172e9ca38aaa9de2342890ca9be24c579519530f96ef262dae1179c76253906b34272e0f84cac8027aa08134e006613e2dd744b10a1fb7215982a5e7b58f7e81b82f657d6a162e812667dbd969da90282a5ca8b298d9615c22521cd393c64a007d7432515df9a23c17378668fd57d98a511f2786fec0808cd1ad8f9ddf3e58e67036097ffbae456889a34bc75b0c28e0cde90c9e03e5279b2de0f9adb157221d3ae9dcf51696852b6938debc25a81018a615baa00b3cb9448799b074c64a8c998db493da110feb4b15e4882b9355ec864f47e4e0f05085d2c00a1d11ff898959a18b49f593a0a6494249a293bdcf0c2f9f9643097b6556287da5826c2c5225cd46609264bc4b2d0f28eff994896a89d968a286b2452241b4ddcae1819cdee2e4fcd2eb97fc056f2570c2e2a758fad57361bb7483c11502d3a02de54f08ceb0815801ebfe9be21490067584a31dba3172f06a54085915fbda405e494a8eba7495e7a63cc7352dec0f896700657c0c74fce7d794d36b2a652296dc9cc729eb38f6274350b205390660cde061109a478e76e4612eb8aeb96cdfa31af16ab4b6bfc3b08ff0dce8e653494495b16c7aaa1b0dacef3ba37aee566b6d54000c550b15421b6db6eb03b2e479bee34727a4950aec11caaf4064dd06e565ca462d57ba104cdea84becd798c1c5278374b2e189bd3cc370cc2a76c424ed025af43fcc7c9240be8101d27d1f8e77f6e7e001c916ac16006eaec7bb9cdc73a4e3e155078d212983719f4a376618c5b0927e3a16ea94f03cc3e1fe9dbc908f8d83d93899110bae3633e50c412088496145420e71192c6a4342a26bb1d2a8561ed954a7565a95a58fe256f6bb855badae540b3ca1d723a9a48a48b4a0317ce390aa3007046d1f536063a0697fb32e757b554b8aa6a8c87aee9cf21acf893bb401d202aa11975080c5c3c874ba944f06fe8ecfe9fd1472bb1622792a73749c665437a939b7c1035f3dfbbf6d4ffba75b34332e4397103cf6246b42a482c417d00d83f802b0cc8805b2120b32d814e7d20e98a088e86e39c11f503683b6c59a84d77e2ecc2673d72608cd327356411df37d6f0ae40f8626d7b7dca004d1cde7e6620ed1240ce11fa57fba99b0ecad20d4d30f0427102753ac19700813885b36565a7dbfa8144ff57abe7be347c38fb31adca30097974a4b1ece436877d50ca9c4c5c15a67083bbf2dc1a23c8f449884e5462f2ede982c20213896497efcf05f82c60106970282e6b71c844bc039b084286fa4a504b899df6186290be49477a04a8b36a5f90b95d63dc3da4f72b78a2eed6cb87dbd941738afcc5701213db79cf54b601ffb6effa8a96d7079d24d46954a5f11d1765a30e2d8f5ec17901b47d6ca02efc962c2624e0e6de1ae1bcd0c8a4e0a2f3a085559b22ca411bd281c867a0784964acc351cd23fee876cca284045df0eaf6e47edc2b76c63e8f54c08d51f652862873bd004848f2181fb5cb59a5750815c73de337565629dd28bcd45cb5a80a73857b74f494776f8bc536b89094492fd10200913c1a97f792423231052c00a10f0dfb4912dff05d57e7bb439ccea74f50c06a9430218e0c38c3371a4f4749a9c000b909f45991a895b2c07b24f28e3d6c1d2afe826d6491fd28a8569ac39f15f555b3b37e682e663aaa65514299fa7081eabebfac17526cfb45105b54246bf40cee8821d88bba82712936cce97dc34abc5b471c541d5c1a55100c5c49a0b9044fb77e3ee2716250d1851e6a235d2f4908bd6b5bb04dd11ae2066b80b67bee26f8e0c8792cf7475ab9eb4f935d0bcd48a5977f669c3d384a1b557764c89dfc1d44faae41f8f4f8a2c8fa70ba52ff758fe8b9c233785df42ae3cec9332390c511fb009bf1f27f0f4be2ccc29038ff165b5461df3771d85f7228d1022d0382bee077ad528d604debcc45d278a48eb2ec118183788edbddb90abcaf6df76e12b0fc9c5d1a2d9b1d515ef0c6224cc67ddd0bedcd8866386e8feecaff3abfb505d303283536d5d20023861270e4fddf5e37ec347af3761c49c7f7d8c04ce0994a40f0a076483167b396e7179bdacac0521737ef01ab13edf8cc950c746f4d043fdb4f9052d4b2796043dec2239d78eda641f038fc94204e086bd539e4027a5a24fe4f6825abefe996bf20cda73f3f2b15e5bf0160151568b64a8c4a24786da93a4966c9010ae0c08813c6a5c4849508038dcc0db2f0d95c8788c3fe4607b887d311c5ae69d60c360833651da27c84707d520e400823ce1926638d416df58fe482ae8074d00275a604e3b64ed10adc889715548976109ecd312517e3d2438ad39984c07540dd06fbe91310fb941877df49e88a3e35aedd0d57483153e3535c6847c3a0d398193909165c51defc4aad40bd16db92e62653a1c0ad87368303f5a86b625a86a5b403c685d0a04b16869019a3733c094999132ab0a31d65da16392f82fb987f25806259d7ed817aaf0c0ff0521ae14ed9d40a8632cc206fed3fc224dab4cf195ab71ff011108e8cb192e3670fa5613fc1b1ab55f2560b0dbb5278d8d59a615e9783030c97830c6fbfeab1937d72f7549008062b474d0ea160be3df56520ffd03b8498a3c23577867c255c1d2bea262945ae8dc100a3f10e029ea048bcd34a1d3bfc81644d0553a5980800c85a1390f6c61583027c38d368c9f1b0c3d1569f4816b0ab8b7fe6afde7822bbd98311ba559d0d91b34ee318344342053d0ea7de54a27ccf1b9c25666dc19c135dacb2f3590f0d869e5fc29eb9a42b83f305e8f193e9d32784c20c0c53caffc4ba1dbef33c21ebe92350cfb4e58c405d5e466a75f22f1cae3f0d3434e0ab7d90a04f7476241bb2058f11ad6dcb866a07891386a6a2a7a221cf95d9ae1d8f9bc90d2b5594ae70c8e056ba8a9f925329652e4ecbad337e42587a6acdc91bb0cae3613a8da3ea8ff7ec344cba57a84830057cb870ab0131c8230b0f5a4b1bf249eb17121bc5ee2d97751c14c69d45821e9df5a33f09d2089d01102306f446c2e1e9733cb13ba986b30d0ea6e336f35cf520f37ade4e46101468387744b4fe96c5ba1842348b499d9a2eddeb7b15dd32424b945b9ce5c156b4b599af7e5fce8ea401ff8d26f32047b84d0d6db5581d214c713f080f80541916b9034e764e492e1b6e9ee9eace3786767c7f2b718dad0912d04079e1b681be8f35f87d990ec0aa0b9771d40838bf13523812eee455778b99b4f84777ba709aee77fd1fe52c24ac5bb901e297487dcf5d07e16a1bf3c9361d1dce83a6dfe1cdec0b447a7e94c024f9ad926a547f1163adc36744c93603b0c2d68267fe404f3da2aa7286240a600f2be8875127045a45bc549a05810b4b6f843e92f95200d998f786d253fbeaea2b527cda8658170b219252fd0281a83cb84fe870ee1de1beaf6bb300936fb5c60e667b3c42e0f3ccb2b9de984be85abe864867b1716db6113cae3bcfbb01d7f2879a0984be61b3a8370bd6a42d4fbfd0a9e69a209d0cc234f8ce07b3405b7cb8064c0b88980344de8f8bfc3c0b09b088bfb975b3dd28aac65607bda6631d17bcb12b952772b9c884201b33551efd1c9f24d71e2fe8cf9f83c9bfb7c2e62b02b584c510e8b1e27696189a2c4af836e7398162235559912ff6039c79c40dbc32b736af8084d162cb177cea3a6f355641b8ef2d871d664c137af39c45061f16893208982461b742c46c972e8e0497abf63b31c7c6a7cf4dba5984d03a177b9072dbce4a907a680caf296e2977927261eb41eb72b84312e10398d4124a90c076aa01d11b55a7834138d5d51bf025231b1155880d16a5c39dfe687010ba0d03bf728378454b5c3cb9cc09f7a421ae766d4c5f6566ebca9ae601b8c4df22135de90cdbdbee5c77806772aeacd930dcb6051657d5e1ac8325303f7ee8c29174f5e646418239b026b26e29e00a8776752fe61f93f80edf0af102c2775381175c497f1dcb9c0ff0a328353784680c35199bf3c8466a535d092be5bccba85ff5376c4c09db042d2aeb24eac945ce84e133d0078f40ef5cef9419be2ff85383faeb4aed8ac6271a9c37a7d2fa4f9d98b4b11ac3020bb58751f90bfb02e8b937f1e5da6f8bdfdd0e70e4095e12b207e9ca0431722456d0f45b8cca8b622a66f32074594c0a9f3ec1712140f04a6457318ede1eef38ac9c8301aeb4e70c9585f0839da2fa4d62b33f7ff6f9fa5887c278a883ea5b85ae11a85e50dca419bc86489fbfd6020b94d64c34d560946eca6e0a5f120fc47b226330230173bdebffeadc8c1553d8b4eb6448959aa84a30e2db804ca68366a957a96aff5cd39dfe9d1261e7f3bf31b6ed42b26e0cc68b1d90132f8664452b418539ebf61361e0707f225c7a515873eca0cbd3ec27291bb7c3c7d656db02bfbbbe4047f3d2ad500bcfef6cd2a552574788a944302fab42b9a61c064bcea66cc1eafd91406e3f7d3141594d20994014fba5a4a7e42584c28865db863fbf190051c519ab026b758a342c407fb489fabd5826acf4662addbbf0be107d07decdbb659f8c863ea117e90379cce559b534306ae782d37e455905d282da214304eecc03afee870294aed699a320f678260d669351d8c15349e232dce2c2545e641421a74615759d853693c5902b3bea348b2c20a5e8997e50e65aef2fe2a1bb6fc5ede0d3ff21bb8cc1580329ae5b52afc3ba961608e1743a1c177ab136117f37505b354468347b8e5df721138bfca9915e8e1134a42bb39ea6762d26b560b7e8db02724e7f6cc5a5ed22aaf28e40379a02b203006245dcb80781296cc43e66656297667d09448c04f2ca9f8ee68ded02364a204e930981e7b2b2a2c613c620cf282a5ddf57aeb0df5f24f1707299d7a628dd570bec95d2e1d78fbc3a0fee86e287e542d36de03d7990d658e9895bdef952ddca86e6ea4a0e1d52442a42e2d4ae94bbbae682ca06c768dba5e1d9cabe5e863085b7dcedb4140e4ae659b950139b466688a12e6c2c249993d1fcbbaccc3113cb2224ed87bd18fdc5d63a23ec82738ad0a1fa15f100ed1fe7155281b65c71848d3ccfd1713bfd259c8ae50073d2990b17b9f36f744cf67c60e2a4c6a92791eeeab0b3726c017f9af8854e9b09a0670e8058427a07d69fee21880c85225b7f8b2547753643b942a3ab08e2c01059bb920438e4f206db48e0b475575831d64890ccbfd55f1e61b39bc19e72541c9cfd33949d04ab1c94c7b7eb0215c170bdd7acd227b0d65b67142564061ed6802c76cbdc144c77d180bdab5fefa3120bbc095a41143a532439502bf2da9186a38bcb30335f9cd2ba78fb2dd75f042e1b832c7185cae36d05e883c0bf57ba077b472426c7960ebdb5340472a234d0a7bf583c6f10bdc0d57c02d4e0e2bc0e8e94306bc00d0f3b6fcd51acc73dabbc92ffa245d3e50a2ac31c8d9b8a72a35ad169a48a5ca9ee50bd6b2d915cb9c1160ebc9837b4c63f3397a6deb18cc6ba6ef3497cd6be1fe64aed3f7b4e3bec16388b9b5aef3193e506b5513f7eec208b036ae3f9b259cfb86f51ef6b55ec7f58d454e501044bc4d76a95da716d826c7ec21017d2df1f16a30c5b1ffbee8d561ff62e20f00a3cb978dae3db79755f834bec970d33f035304c286f833ed14f08089fdbfc929fcbfa5305b5d0403a977138b05aaeda7f1a6c4e95a49525d756a31056945fab67403ba1548d37d0b5159d6f5537b2b951d098b45cff1831dd3b790beda3cb8fda253f9f15030e5ee09862b392b356a7aa03316d2c1a051e45e4ab71e728b9d3b6643fa888203fe58a9249756616212c0ac1f3734df659cd06e550f42fa5cb39ff397b4b8c5ca0e27d002b1abe4c64c331b6ca6abf55ba899351d8286e75fad177cb9f42bc2180185f0cd1d37d6ffa249a46304e2f04264ab6b8fa339d80edb81021eab30274bf826664194cacf692709ae31edb63ec5edf63b420d270a9625ae2e41eb8f48e68d9811fbb46d275af94b4ad11a73c176da1a8b58df14b7b6fa601766250517bf955ff3c40c0366a36cfafc264084d77a0ad7bb5bb22dc659efdb3f420774c2047d938d91c37c706ae44aeca1e5222da3be7028ffd761c218a31f7f6c4c691a958ab5e3e1013e28dce73901daa1b469f3585b29b4a8c6da121cb766f79638dbccc5af62e5de3e9841fae8d69c769ae93379def750faded555f81d080330d1cffcaea683c5ad542c54be49391e37a916877735b6d0934aa5b588739b251895890f5b9a1ff62bd25bd8a4b7cd96b9ae98f9fae37bddd07a182a6fdf289d2065347a7f9675409f8a2c6ece39936e17641016aa228ebf421c49d439f344034b2dcda2a75f01694f71f9309684005c017b28fb78e9eb166e0020042c57a31690dc8b9d9813adc70576393b9d512ba289d05cdb81de048eab04cf359840192a02a4eefd42e7620ac669b6db8171c87f99a4f0a89717e90c733320db91e1a4ca9189d4b4b9956c19def14b3402bc282417e6f9efa013fb5aa579775bda73cf449065f0e34adef96c1550fcabe499ace5b64754d92f693d56e4505c1d3d022eba5680a374a15d93a952b8b559d1e44d3d63904ce5e50314c97d5c6cdf4c811c7e807ab3319d8bc41717430ea7b613073a20da232f48cc1ea0f5cf99a5276e43ea2926c0d9ca4e68a5baf57cd32a706783964a60a604f9a20d6398373239660bdeb5018a6c1efc9670ee12aae8fc4d57c8ae98c16719b2e01f76a96465a78d47f26306e455e9efeb4673356ae9609dcafff15a20c90bb198278738c2ff15663bd68df52cdeabcc36fee1cad30d5851f01d0b32930aec7c27a144e40b0eecd27033a4bf791c2917abd87ce39a3a27aed55fa6cea95276e4eaa448e160ae113a8ba28d1623231e4f0125d298349f2549aaf24d3bbf19b4d69520ef3e5057c4f8bcbeea11a5ad54f4c726e59da9ab543842a9c268f2ab40520eb306db400efb99b0ee646a81dc2dd122162f792747b81d8f93c7caa91510bf29bb15d552b321fa8fb81d507be16ba63e82f01fb70d4a472f7be32b5b6af8bcaf2b84925cf41e14d847733cb0811d2cc6ad9a78ead63acc69d4e4ca934d5a17b6b37b9dd75a404e4e46d28b4423d0f49833250be13da11331092fbbbfd6c6cc4318f4cb056e10e1aa5aba8149648677458f6fab06b5e7badf3f7fd434f6aee0aa6b0936cc555edd4b02eefb6fd29842c5576a4f5c9690bea525fdf565d4c4c269c114a418bd6dc957c0436cf4c0b7756f8135852ce9133a142dff407eced01d67e52f83967fed9f009c59146c82fe0be0103961164c7ba6f612429360f899f50d538bbf1ee2c4b168ce9bc4883ec6d8ad9e0eb04d55ee2a930aefd7f87478e61b1320ea4fceabc71354e5aa83ced74e5805266b0035f58684493e63e47055c1754b1c0282fd249a03064a7e5729a78bba452a28921a6522d71115f049bb1bb76151d6688664901ca6633337880def8d1075541cc1ea97d4fadfa52b69127d2045fd495c2e961f5d10fb826b1d812a84e6d370c6f5db9eaabda7a9a9287c5c9fc56d2ee6f6a1f88925b05e4a99c2097bd5ba8c458c95e46167f1cd9037bd773556f0cb13fc690350fc42a47f6802135ba2bbe9b4b369841136b1655df9b6342fdf6bd88a541bdbf8d21afa400073e3da732d7dccabf41d31f5108d464e2227df3d5937a919dfa15d1d564d12e4e7af9521a3f5f390ec645e164e61043f63d645f1887915bb74f510bbc7b28b16640fc446cef1487fbbaccc337f093a4665db83dcb6d56bd3bf727197f0b25d1006ccee766a680b06d47ddcce3d217ad4464c43e19ec781b49d928799c9b6fd55c801cd68732b14cb18120f3a6e4e650c16c967bcb2a268d9295069eb7816cb76990e012fdd4dba59a1e841ae717f0b52992d073184a209ab29b3c7f005ecf20ed7e61f70b93e8d6a7dedd9aee9a30f4c617d2b54763def414d2007bd5dda5bd0df55cc2a3bf1c7722662b5bb56ce17f73bdeee52d256962bfc5c64bf19e59dd500e58fe9760d5075d6b08ac9131b68987d78af699431c946dde35072be27e2d3b932c83696ff37fb6b55827ad324f167533fabb268c3afed76b15a8e085a5770a3213d68dff07598c02da64b9fef5db7a31470eb06f17c5cfbd43bec2323c15318569d9b11a1b433446ef943dd031a06207ba13c6ee0a2352585161e246fa31db33f60ee8a9f613eb710f80ef0c3732ccf6175cc8bc47f72c201b15d931f6dbf8bc566d9c83daabb72a45e91cb9e6ef0bb3347ed107f606edd05de32791df7a1693ef16fc5f957978dc61a517a8d5802992b4b93e48a0db267b42a25c328832993fdda38de1440d76a11c04336e4109940908de68f3a4ef5b4e3f4d141d4d1ebc6ab9bb809ef42c93493207711e856377b5b9fb2de64f9656c014acd7ee4a5458f1e1e6121135eb735221ddb7cf718c861ca551109a852410ff41b039592df20b9d725a9b49e3d03d4f957915721678989a5657b4a66bfbd65138162d305dae6ce3114774f5db7a1642cef9ff00f6180bc3fdd91370cfeb415c50fd418ae2f4ea88d4c99e890b48f4eabd7230716f2b7a374afdec962026edc151a28ca5feed6bf9f604551b34d226df57a8a8c44120e57b0e4b7022f4635041e151499ad7bc5aaadbd7fdca3853e28ee4a3fd145a2e2bae4694f022ed5baa76e07945ff87be387e6831a7db1e4ae7d2414d22668feafaa5fe1d4d00cd9bd3b9f392834084c110e77fcf66298fe2d58f80a0a751050bf340280f30bca2aae93d717a4bc9f1e449720bc031e891ef21d597f7a3aed64c65aacdf987672621141c95ad7d1b86503cd5b2cb60349bb0340b4c7d62e9a2f14bbad3bcb16706f5c31d2f66aaa72f03e3a165c8fd257338c15ea06d022d4961698ce19f29f19d8478769e2bfc7f5c7bd28f6a2fbd0e5a7d9ffd6a7260292e378404e51f71182e32feb6c9066e85026550ba5a905eac84f0f660010619816285271d1169b306c93b5bd86595c49b67a8cf9f5054f3341c3eccfb28ead05acb8e28cc336fa9910cd74a507da14fe1966b7a00fe3d5ed548a107fd7af3b8a434f855eb51490e4ae52f826df2adb7c0c0e37aec6a7df41c3599768493192b56e6dd4edffa4946ddbe7082d2890850caefc714e63f55204e82e27a7176ff976f73e7a407f6d226853d43ba37087bba117621318023eaf6a04d5ac291ccf6fc3df2cf7100ad1748fb86e403c34f949d5c382ad87be63c1684bc3305afc404765893f5068a99bbc455d4240c55f4d83980ddd10e5a887bb52cd50aff1fa9ecf7b00d09d6ad6ec765568f4e7f6f0e00298c5ca56ee483bf6f79640542121a17b7eea721f9024fa768f9eb384e98c93c2b776b033d04464e5b04ee08120eebb9e5537ca89a8f39004aecbeac17e4312d52813bfa4b4f0969b1a0fd07034c6f77ce87135a65a5ec3a29bfb11177da5400224464f350b5f099ba10fd9ffa746251790d88b34280a1d3722045c3b12e4d16148b764a0b86e5043675d33cbfdd8a08f15f07ad7a367412f690c14ee34b8db6ea8067ef881d0ab60792b835519a3b4eeb458d29d9c1c0c045ad897cda5c416865f7c83d311b32ae8741ce6b6a5b021d298665b2c829d2d88eebb202222b78a592ab17dbc5b6029024f15d46fd5ce6b1dedea1ffd0f824b0c062ce17dad3f23ca78e8c9f6806a54e41ad13bd7b140a6351d697597300961322fd6c556e4cb83cba6a1c4b67391f13416f2c0f5805a3cd32da95254c06ead1a5d38162a1b32e4fa169a90a9f55dad03ec19188340b1002b0390db795588f93f57050d12372d1bbf89383c44504d222fb3476a7cb30b6327d0893d01c75a87bf04817320e2c3e14e164a77ea05f4fcb89ad4d37df319ffa57eb23e01a7343a4de476a96d2677238aefb600dba8a713477678abc4838bf2835f2ae541f6f2ca82af66078b666124bf0a30c84a44f301589bb7a6e9a05c3361b2d83345b3e2c2d6d57c4a9d43a1eb1d058c7e15c668f780c49e5af9237b561885f2e56d9a948752986c551106da68610b961112c06c356a95d4ccc6703a657022aadd6003d08c11124163425c486ce5ba7555ef87b12301bf6d88ff59b9bd351504893eea755114278880e22776e4c242f815930bd9c42453a447c18430ba9a38e5e909864463b326929f28c38984e914f29f553e0f502bb05e38d23e892bc02598f99476840b0c6c43808e619f3b215a07c5f19d8ec5082970201e0b230b7308811146a024a08195e92e91188e92c58cee96a9aa3cc82d53d36aa54358e53ab6e0f15a233da55b7d375735139e9ae8ef6c6a0d35476df2b8199c5149a11fa48341dcc6f773ee33fa9c27407ba2cf57ed9a6b011533ab4d891088191f466c895a3b7b374aa4468f7fc55a9fecfb0ebe5e442df1ddc8b998a7662076d20e68298cdac3bb8d991312d30ea5885732f52b0bca8abe89346befec240688dabf59e86239bf9c11eb5a321165bf670861382aef72abfba207b31add1b89b0d2e17860142118701014ba436a577c539dbdb49420717630af3bed0c408afc8d796cc3f80a5d66c6bc09a7e094178bfafde56b6fc78b0a536737214f1a9ba5fe9231393750faadff98f5e4c79dd6442871a90994020eccf51fecde08285d676114f05f83eed6c3114b8aee2131d98a12c790232a3cd88dd9bfd25b59aeb01e2a7918d78b7de643d81ecf759648317a97c9ae8865a4f69319b680f68b1384c16ad243f08a503fe7ac3f02ecdfa7668f4e7a8547f673d3c1c41fdf47697f0402b8b126d5693a1e26e7f3b58bf28534701982bd946c55792a4a2f6f389ac056d9360077344acfb5c289015179cc0a0432e40bb913a3892eac7a74d87dce87218c91ee910384cc01caa9515b5d308c59361d131f6a17642f56c15b5c455dd1ad3f526711bafc0bd411585190357b73f25b7ea5464cb49a8857a24d658e7560088e1f0500474fcf1d07417416212627ca8658d69959b34e0c6eafab86b13ceda51bd97bef2da5dc524a19ef05ab05760555be5ecc04df1756ce210e01b8a0e162c905538895b4d65a8720931430296d5c43beb9a5ee563c3047968a02d48fd65a6b3dc6e9d6aa1455559668628c951fadb5d639b808afc1c606ce060c2e581766bd81b801d6f5ea440715d61d299994d70b175ebdf1de1bf3f14b063b7e453bbe8a52b8f9dcd0d2baacc195b3ce39e74ca4ebd62a11af90081b5e04a75304c6f72889f291c7bdf7de5d5166b850c5061d8d28384a6a1cb9cad1a426062a514d6214ed41506badc550dd5aadca125131561e2a6ffcf56343a77339e79c330fba9e73b83548f291b193d12403aa2a7c1024c3e6d62a0e069a030df09ad15d144dd3ae208777dd52a2b9c055bb115ffeca39e7245a398fb8c48952d60cd52fbb50ac31a66e9c6726bfa6db902a437ee89bbcc1b0f2bd75b80107c90db1032cec9043c693a71c6a58907b2129063467aec0d00333a6049386e70a026edcb48c9b155ce56d776bb5a9e806f584974cb07a72b6505f51b7a85c9492ee7b2c7861eb0fb1962ce12ca29859e01aa2a36853662676d885fac3189650714003cb268d5839e79c7352d7ad55afa224364440a02e763a186badf50ca95bab544533aa7a9821ed2da7b4afe4605f585fbd2f2d59ce19e39f6ecae0674588c04f533907046badb5ce399716b8c00ad9fcd87cf170cbb987ef9eb06541467bf87ae194abd65aeba83bc5c37cf7de3b0b1b2db8ccb74c81f8045c74786f2e3f9c4f97b461e0702e535c471c33ea0fab54a930a48236e16bc4ae3040a1f112a2fb99dd65d2d402b8ad01763afea5b51eaf8662a6679bc026433e84566bad5fe0e15a0d7786c2bc10d3109622eede1b8c0d7def7287a9e3e51c06a3b2446d4575e9700929d0c5e44c0e31bcaa36a70c37a634d1d24363630ad50a3c1d8cb5d6fac7756bf577e2f7a565f0d8d2b5c63978205ae6cb16a9a02564a2069dbe53d54b09b4c2554fcca0aba81add0578ac90430193ae3035d0ccc38d2e3e5e086285460aa846486d2cabd5fa80b6a03486387814b57430ce39679dd3ad55a9221d550f5d68d928fdb897299f18df87598428c206fb838dad208ea243af1b18e22aa523eb0c555f3e38e9c278efacbe3ff4ad613b85bcc4b4e4b424b544b554b59415a591e89c73ce166474cb977bd56e461597aa1b420d4eb8c8016046d345cf4c8926d46889545065cdf12a6801bec19183239765604063a735ce92c29a14725c09692203cd3b60e17a01873128e04285e6283de7265c5538bdd8962316349d3e94ac3661480e5de79c7316d214963987cbdf0f086abc85c6add525262729b06fa1fab926642da81fa5df8e8ea7a35379bd5651f1f178206dc838bbe9cb0d06be9e297005ac6ac2b7c40b56941f593940d89011f5638d8f8b93cda2b25141c80bf58751a2c4fcec2ecc1a47c4d97b6f274e9cfadebb32bfcb06c46b7a3e6658a29514a6044b5266089172c2972e3180b961c071c46163bc42ce39e79c9bb4c8ea393729e1049e1badb5d639e71b12327cf64806901584acd0e4c4aa09981a9ecef4e0b2e29165611082a487d56ca9e952e3c5949b69adbfe461d75a6badb5dec1b8b54a6389c9498a2a9bd855e51ca5c79d7306cbf09043c66ac1705a60ce39afd9dd5a6d82ea6ba6f48be14c0a901b5a5812e5094d4e120a68bcb0c3862027407192b9c0b17031cc9ae0b46880031691d9606525cbc8e59c73e639e79c478c275a60d526a01a5036f7a380940e5f44744bbe8841778c1e60c81063052b63aeae7e58ca0c8d7e64aa7e6118532d8a82ecd2987081b5f7dedb0216421e96cc7eda7891f265cc95953264724b9cb098628292274d78d88a3979b10203f0e6eebdf70e89909514453249b83316b1d65a9fa1716b75e9c419a69d17eccbc7477ff5eac48bd4d72ef7de25c677ef8dc327f66679227634c9d0a899dccb6805a087ce7534507befbd614bb20955eba7aa3141c4cdfb12c512eb9d28ce0003b5d65a6f9005132eb0cc310ed0836b840f7bf9f17b2d8dbbc4bf78e0f87089a1804c94701911446b0574adb5d63372a289194aa2186ead3536a3b5d6609c44305222182a114cd5093059df8383795c34698ab6d4f47beffda5876b35d4b1d248a955b586478b24c5ca244c16ad0a7ee1ec4c757af8e0ffc44a6dab07b63be1b8ff6c9db15ae4e8987f7d106c896a7d6e38e8008be3149f1f3ff8fc2fe835f669587eca3b7ffeb506fd3c082be24fcb183c6b89d9d75a2fe8b3a7fd8515d16666b53e37277f0b2be215ab85988e6567ec4c43d8599e29efe94f27cf544787ecc171279938eee4108e3b4984e34efae0b8934138ee6413c79d2cc27127a338eee48ee34e22e1b813e154849f3014a100fd7ec86bbff77e0a5be26fdd9e17df7befd5ba02de950a7abdc2f1c688e8f50a07a64c885eaf724c5837bd5e9550060c0ebd128634e97959e16287d88f1007ed8c5c63af572c7e39a0e420f3db3ba680e35e61175fadd8726d897a43eef5aa459b0edab3e34f513db0dd86e099f2af25708a5ab38ff1d9896e09dcefed4417d6ebd56d4b4feb2ffcfe838376debe077050c5daf1a0a6071f5794aea0f606e4471823524aacb650fb297050f5fa2dbd686125648717d4cec0c20c0e27284203122cd4fe8e696f21ae067a20adadb5d6b7f59ae1edb90ca8a52902af4f9868f4fb29689f30e13aceedb162c87a417a6f3d9f3c46f9f5ebd3b63fff112afe114afe110afb2354f647e8cd1fa1fae431d2b670fc23b4fc2334f64728ff23f408ad7f63d632bceac84cfee331b30766be0e3cfd0e7ed0f0faa487afa73f0d104d22333cad3c0a6c1384333cadbd0da4f0e2f779d2ea754d7e7b2cd50dda3f5b85758370f80643976edff6acdc20dca864f5c44c960b847d59ddbeddba4020ba7dcb7581aa150c4eddbeedb25e3708a3f6417c498061b381bb40a9d5599d2da1bbfd115c207ba5dbb7366c89fb36053db5b95814b73a1c64a96acd7e18deffd0ebd50d3efa3d537ebb3db55baa6edfc2b84056d6ed57d80798e8d60becc9810b3b02af56f2e51317a6565824203e2e6c1703095b1c047efefabc7aa05333b87f7f701ee21aaee9addfef765bb14b16be24e19823e9074f2467d0a0bb8d5e61a490f50c10196ea51a344078f7ca95136c88a5bc93dcb24921eb86e90614d2f066d7c98f31371c54a958ade0e1941507612a41b414b9dd1b0e6d76f30163bd02ff7414f08529adb5ce399736d4d99eb6d9669dce87f7de1bf31caa919c288aa2288a4bb756999ca4a8aab2ac68adb714aba3d16a95111a56a7b55ec1a96b5d44bc5037679a4c6eae57272990ba39cd04ccd657c6c1ba0ae3a6b5d65a5741f1ea5a83a1d65aebcd057628341ca8d0703adc0ed704da13766077a2820b539514bae0ca7ce90013305d4ed831850a9519aa9a72d513ac3a75153979f12128c0e5ce5939b1b11a92542eed9430756b95575402566e45cf2fe202e1e808e12ac75106ae0085a95a9bdf4ccc9322e74cc7af005a00f83195c380cc121926324eb29dd64d0c5f0cae18bf1e49d800279076250d47c20481e90f6be01969bae14a028310998ff7fbac3fb46ac213f10cc6d6add5ae22185ebb2559a8d35a6b3d2284e282fa417d652551556178c26d44881853b81766ed81e801eef5ea84ea0bf7305eb2c3a81c6a1fdee1fea49a4093db20e9073a2982024f99a5324c659cb28c1a39e79c731aa95bab5427d254e51c15b60ac891260a929c12571689146698e25af3e5051bac68c0632aa7b5d65a6b344db756a14ea0990269fac34ac6051572a85ffcdebdf786badd5a85ca859ce3ecbdc717204e540c1c9e5120a97c5609f3ba785e3c364c70600537966e30dd70e22166bc2668894f7ace39e72f5cabe158ea1229676adf7412f1412e8aa2288aa2f824756b95aa2acb4a962d8d3e75697d33e50026da34812e9ab2ac05ae31f7cc5b2c8837a38a922d563428c1409a232e4409594384c9caf01483a7eaf0b3b901f0d0547134cd94e43ef0c8d91c0c8b950d0bb4d6fa85d30ba9175460a8b5ee81a46b9d828d13a6ec18f284e3090a2a389ea410c64c159b2a1d68bdf3d65aeb5defd6aa968feb87eebe6eb9831de3de7b6f9c2d7a8accf195658c40e156c20414ddcf926e69b7d494896c81f60626adb5d6e393924bc8f7de7b4b9fc0d2256ef925056fbfdce2ba4125de7ef9bb405e17c87ef975814474fb259b0b54ada4e8ba7dd80d9623c5df96f8f565bc5da034e412bd443617c862e9f647dc05b2358021170e1a7bfcfe175b22ec126ed912f70c7dd6c7be7d22ac00f369147fbc6e3f2c7bdd7e024c1ce6f17a091347deb2b2740f4cdcfd7535750498b8116a8c531f9542101ca7bafd0e4c9b45c32dd6c77eac064f43aa2ddd7e68e502591abafd908a57758376a8fdb07781bedb0fb5aa07b5d61a665da0fb16d7a07a468d1416a468b081051af4be4e0dcac13165a765491b1d2fd07b61bba06babf410a257ab2fbe4e560bf0fac4c756e7bd3ef1e175777dff571cf9e5e3f0c3301fa1e3e1ab7832927d7e15b2cfbf9f8481a70dfc0b7b7d3f9f36fe37c73e3f0ad9f3e709417f7e23fd59e7e739f6fa51c4cefdfa54f1fc51c84e9e39ecfcd1fc54117bfde90f366d586badb5d69a070291e681f84c816bb1c72f3b55c41e3f599ab623d4f664247bd9593e68da9e640f62d8e347117bd9f384003e7e23f031885f867942809dfbc15385953d8ad8e9019cd69f9255baa97832823df82aca1f5f76aa803df8a9254d1bf93ce29b003d4247d3f614fbf1e4116de0f388e7077842281f7c14b08f3d4f08e3836f343e78da9ec4b7dd1f5f3c8dc08f9d36fb68f9e31b8dfbc737826d9bda1d90f2244fcbc3b5bdef5b5edf6f6120dc27af12aeedcfa6ede1da7ef26213677f3e5cdb0f9ab8dea7d6073ede3f42c7c57384daa67320fcdaefadef1f2184877fdf92e1a9020f042a7adf56792809ec93e0e2ee5bf4eef01d5ff070b919ca13827dfd46f6f51be5479ae1310de70941bf7d14facc9140f7db47813443f7e70854d49ef98c3d120954bf3d71b9192a7b2412a87d1b2e3743f5239140f5692b3f3fec71b9196a1f89046ab33d19e9b7a76d7cfdb62723fbfab4896fdf36f2787d59cc0401569a4499341fd7c09f509b6f7f7ffef4c333690845f5ef336908cd9f4f15b6a70970f16b45f5a9223fdf2f7e7efde299cf14b806fe8820bca247688e049a1f6986e6738c81d70f4dbbc335f0add9d4c1d757bcc56e6182db0dd740b407f489045bd1fce908207c9f3912a87d7d22cd50fb15cd27d20c6d81dbb4419f527bb3bc5e016002f2c8de8c0385150b17bb3040560083a14455d11124861d4d207075848718252f4945726d5c28198204070aac7af59a823223cd958c21301cf9489324868b315874703a428285cc1118a250e90052650219a72535b4209503bac315aaad1e16198480313a924687132d2854c4741265d6f470d8b16555e6848e17389284d181c4a3a6e932f3654c929d926f03548494c0a2e9480d23dd02bd2b6a586831d2c5c9034960a6b8e89a71e38b04baf0c0720106060e3e42c0a207173463bed2789558ae74482dc9f242a3024d8894d9a1842c43bc1091bc066160f58293264882dc5812549d6c915206ca0d3b2532d0fab5d60f6bfdda49f2ccaf4414233c349b1515614be82ea2d644541f16517d560488abef82e0dd3f0c027753e0e864e00259ad6e7f0317a85a65d1e8b58bb4e07dd01c3184f38e2fb8a7085e0dd7dcf39ef73cef29b39d361b31e686eff47ad562c8962c86dbb48195e42886f52a69c7dbba66f0d68a6bbf7febb52578f0db7ecdd1fa581ffb312bfc8f985c68ca71022f59d070314f8015a58a9391362ff693bd5eadb84186675a801efe5f70ff824bd672065bc0f8f7dce64d178ef369f5a5c818dc39792acf1fa126d7f9143888c768e7797ee7e4f9f015ddf9233404889c9b1c1e82ebe48090f19815d770fe83f966cf165e795de7038818081d580e880ff54b163b7ceccbd7cfc339d3cff9b4835ed39f5ed39e5e9f7f5105f11fce34fcf03867ca713e873fff1156c4419c69d8eb8733e5bde29c3f22aca87ebdca29e120d4fa38cfbfd610e3105654b9696452e0f6d81f61f0f427bc39d3b087b433e53dbb0b54cbaa12eb0205005684c91984cc20049c9965afb4628a50f67b965a39f873c81c324488105f6145219ee8eaf0c0437cceef3c4fe9cb62e5f6d89ead06fb80fc6a7d2e79675f6d09dae313bc3db327b10993e2f867b3d9135d21f3da0dee9395044bf003fcb4b35a9f5b4add1e3b3379685f7b2a42d9efe73c69a6f8f93982ed1f84f84bc215996e8f6500f941b53ee5ef1475dc1477b2535c8ae9429825d6edb19f639655b7272da5ba7dfb376678ecd3320cb737a90b744f1dec1684056beafe303ef5cf8747f0e044a7f6d148d9ed47f0e04943a8d00b3d2ef74943e8d0233d2afb74df3a4df6d81cc1d23e94f1bfa69069b3d96c97db3a8d3602ee341acd1c793273a492f1709e27bab29d1dbef303d8d9f99d1fa96e8f7d9a39f26e8f7d1989836aad195c1c1c9cef813180f6382fea4ade05c2f9f044779cba4034aa0bc4795cf6640d5624c39de6804a93d1ce5177cd91c60176524e5a7476e0f747ddede9b19f8e4e7d14627dec73e0234343081621b6c43dc7333cf696f3c8388f6c67e789eedd7902c08a7876c6ddedb17f801d9e9d7364ba3df6751bfcbcc6252d563cf6e9b8d4ed6bf0d31a95b8285d20126cd75da09cb73fd2b840b4b7b2139b2927c278f6b49f9d29d8671fe2b9997ee74f9a1ce85cd0317e7ca6e4d3fe9e297fda13e199593b0fc19fe8ceccda754ca237b712aed9d741c3875ee82bae9167ba952a0c04dcd00bc9fe9a290960c74f93fd35d3cafb799bedc968c836f4b521da93e608b80d9b9ef3e42d6966d8c95b0ed52e645634e765e608b8e7e83c3ed3b3631e1586bea2427fcf9404b0dfa77d07b007dcb35a1ffc3943e6deeda674ef3a99fef4fb032bbafcac21ce54a793e41b005644e29f9df86567ca3b7e7ea63afde2bfaf01ace8923433ec3a662ac22972dd1e2b765d331da183dfaf9df6f7af997ae09ab4e7005644c3270960cff99c5364c1fad8d719a09755f2b64ade961b1b7cabdbbfc981c77e7853f67559b47090f81b5f8ba765afec5da0f1ed9756b4743808a78047fa419fc8c77d7cd2075674692709b8e3b05718798e5b788c674bc4de3e8963ca9ebc155644e318df4b3e086cbf7ff3f84cef83782efb0afb40f60177d9ffc08a4034a076d9cbfebce7d6b022f2ea4625648e62380811267b52f4ba3df6a7f3e9e97e4b17c86ab940b22b7b2bfb9bbf4f6e58d13d65b78431008469fb8d69fb07d3761cb376b22c2d6c01b9e3e73ff4292e9ad1fe9a29ef77e88d84de36f4e499e2bfe1347304137499ec89ee9029f24c710ad7ec2c8415ddc8de147bb7c7be6885440e0e386b75172815b37459461f8f7d2a667512bb2e1e0f078d4d78ec33e84dd53085839e7836d4968af9809ad27b32332406120c2c9c98544912a48a95960ca84da1aba18549872f568088008c8d193d2f4994d8a005f0798080dcd594d3b5b356644164c19618b7f0d8196e9930c5b2875a10c38ac8162718bb702cf42c6c16157a1bfe0ebec240103ac32f7603fb4477b6ebdd1ac0233c0d7fdd47fc0dbdd00b9d36a11f1afa23744fdd1efb43e6e66db2e4e1201e23a21ffaa1d336f44427cf3d42b1b696b97d65cffe07163c2d7b200c9de24ed4e1f4c06d173a71488ff64fc59faedb1fb3f070cbfeba8a2723a2177a15fdc46d2e5cb3436f3ffce1201544e7fec235fb426f7f44903087a6fbabdb1fc1c42b4a74bbe538da1f87f483127d8ea3e8a761aedb1f1184e3366dd08af6134774e61e257aa4476d4f46fd891e3d911e253a6d6113aed9efa18e6be82b4af469d8d4edb7f080d05794e8447a9e70cafad8af5968f4b005abeb53dd7ee9c3f64b2e6cbfece1f08783844ea44785de7ef8858386cedca34267882f092b7b65d9fbb2879ffc0a2b229fb4a1d2c32deb53f64818ee02a5e28f24cbde0d92955a596e500202904ca8644b9a6b52142a06d10080000000431800101c0802391024611c2525fa1400093aa48ca87c483c1e8f04a3503010088241814018080003c100001010168350501c253b581bee1e636c581914d99d5f70e98edb6d32d866489c57fec81986cb3fa23682c5e1f43bb69e298d0e81be5dfb00a3843e3a845a4b7f157bb3f2a2522daf436431c92bf1f1679d970abe9c0152160dbabd1dde86b8b9cb017deb91f0531217c9717a247ba3d746a79dbc15eac3a57ecfdda9174ec9b57b2436cb682d41348a4b122d443d9c124213c828e9d2a228f4616f0d297fb4ce30aa13f7baaa97ba991d5f49387b1fe086be7b5ae947dbb6e2c9116b29f2629e97d18264e10ba0f5eff95fac5fcc103b372e70527452d0fd169a18b8c06395db8b5ee4f630c9176644293dd8fde505776f14e61a5d95b8a79d84c80f8717259ee4030c37e15632d1d679cca3ce59344add0c8401b369179703b48a618b41f79734d1a32de6b0dd1b20f9d7f64967d2fb57174e1caa016092414a53ea392eb87c817c32797f88df60f9f2a0c134a02de68cd9561bb15b0a4c2d2fa620b5acbe6f62eac70c610030b5936c978f8804414b20e1c9767818cc1f2ce8c47f81e8333a07358183c2afbce9ca06a53e2b230209192bf1d21078b13d7915eeae1a44120df85098cd430614b328a0089ca154d49bbb2b5bb75f8589205ca3b8d8bae374b1dbdf69d03f0a6256a7eb0fe733434220a36dc4b7d3d2432173edb71ef61542f6db4a109455a5ef4a34fd003b9c76758cc18456f0ca3fee0a054495e34c7168119cef567783578e091f15e87f171b0343354451c3f97810af0c36700468c3bedf8de190b98456c223e3e33ee469b502419d1450addcdd846bf9e2c9758f084470dc5f843e1fc099911df50b615c7c9602aba53dec3d1a8e5207b358cab701fe48297f8631e1ff67d1780f9525eea5bc95e8f2957a7bc2ce771738e77eee7079127c7196cb1e34e771145b0dd86837039276b4ca41385798d8bfe37eb33adc4b7935e8f2faf35f92791e6440047d86efdf19c6d6b7d063dcb32c5a07d4092f7d92669c08bce62d1bb6110f41b4527fbcee7e4ec113f3dd7d8e2d2c28c75caeecc5a08fdbaa23a8c07f961bb8d1ac69f740b2f68920b3c2b199bf32c9d7a8bb282c4b353018ac83bcea6e16360f681120adda71d6e4606f8a3a240e274fd78e8b3a5db91e3636882a81ba62b7f92a92048347a890c30d0ae1427aaf32152992cf65f039960f445c5c1d7b1e3d7c2d8c965eb3359bd2ac85741c956943e498dcf8ffadcd0083fe27e7b7f2841d80523b578b8ad5fb29cbf881ab906239d79914156f83d2fa4908eec5f88d11b3d84ac49bd4737b77f2e909adebd6ff4d404965ab63977db29c69b9f510d5016d8ad24889fe1bb9a88a977adc23783888db6731e0b9f00ceb13713805ec834407d74aab930a103d8e968acde10054fb23e86c79d4bb50585111e9b3e024e1db779bbb2f6d757a7f0d4e2fc693fb2011384eb1d3fd6fb3df0259a427faaef829677a894247353b769f3540868d4ae86a8604a40706a3da1208b83b11fc8734114dbf7871d2b9ae27c54d081f5773a11d93cf88a4881dab2c2af43813cfd88fc59df91af7995166482bf2946d69b113261aea50fb12b6ccac189c2ed3673616a5849ffb9925a4e8dc638baad898da5f85cde83069c81c32471995eb1c36ca28896c8cd1536d3d096342a475c88119217408b1ccfc376aaebf8abb6185fbbd7a4d193ade99c3d22628040a7c89391848ec01956b4de0df050083c6252da9a415311b72c0fcfdd377c5bc36917c7d2bfd4893254d8c6eac37021a29ef43c6773b079a67581975a527d9bbb793444cd5a3f59a923f68f0f44a69345af6cb8b3bcec844bbe6f2fc15d204820d204c0dd5273f0ceaa9c9680fc0e370074f8b689cf1fef6eed28221a8c762a8858d9292a58d1bbd3510367113dfea966900e735d581ab963cbec47c07b0bb676edd2cb3d856f81f35fd24311d830c61ece33f011e1e7365968801ace1ca7b072cbdbe7f5890a3887d56ac18d818bb8f10d8417805894089b36b499cd40bf8802bac62505e5c740315bc6113b0eda086818f1631de60ab3a6d0ca91d1ce10a211fef7782ab1a8230e19a29572e1711d5be67b36d1938ee1571ad9c0c7ba1c2bd94d1a94bf8879cd94517cd444f778a513861aa78121950d48c5008dbf6fb8761de7fc283ddf6d9d8fcf256db0895d88519b4003d68b1538efe9da325129b8517dccec35428571850a768a03b1c5acac1d4744ac010ba5c7428c9b7ae8db7422f45c1290aecd09ce8cdc1b7ffef54ce01a85fe725e22dac125168c29dac92070403d1f200bea0a7df061aa04fc03a003839bb0eb929a54c7fc8f1822d511e7e3dde3b4afd1890b14fe944d30ecc9e0f37db78c8382f88b6d94d9811999b03f80e8a2f25f5936d4267d68042edbf56e3eada5294f02bbb7f11f1c9a4464cc691fb39617692c9b9fc160cb6ddcf6bf4ac919c2df3344bf72d50c76926d9b85cca37f61632707fe68d92127a6397bbb4ce3ceee03dbf646c3b24ba10cf2e0717c9dd5fc42f9ce18e5b19e78b9c198ec644a1c7a23d33d0a4116711fb148de3555a441ce2f4a85da2b64b9aee74622473d36f73ad8f9ee4952693f94899045622754582a0310b12e80ae11dfb81184ea27813f76163652995c6eb4e83a388719aabbe7e60e545e934a43f023d0ee2348b468e6c0ff68f896750641a9b031d10648ae253732fc866f6146983bffcdb2d2e5c795bc0990bd72b08124ec1e6ae4428837e968e10fd3bc0dc18dd83a6ea42fd8a4a047d1ac9a0390b18e84ad8e9f19ad15f0bd7ec20a3a40a68ba2d41b2af264ea1bed2ab78d0500575b9bbf1e7c3dbe0881ab5c8b2f799e4174ac3f1b4280977a1d22a61dd0a5dfbd68a461dc8765e8ed920413ed7627310a7d2d46cc01609c0e0a45635d8f5e691469feb48f9f529ea0c514fe91bfc4e98be65322c046a69082fadb8d5b70d58fb1239d6ccc52262b2fa6af2c5faaa5c25ae5aac5dd71896561873d220f994c5f383e846a7a1f2cf0661247d85f84d4817997b31b892baebcc89f9f7b3ea8f44d233cc87c1a204e2ad6aa5654437d3c5328e739bd7dc8a1b8a063850790d9f3321bccbe98e2b7a95cf979cacb26708d5f96171ed4f9079ae072c1fc7b4700a017dded8a9eb02e182f2f11895eac72d6767d66e81b7080177f51d877caee7688b905732c34a55571d2def0b58f2335e056e5643992dbc936b9899563ba76fbe95695d14dee07d666ce19509630f2f4851261d99697db948de74bc59f933c3762aceb08e7e5e805612f6f45b02c76e31eb96e3939da0f67c57a4e250add8a87ac5dd11994f73776f6151f0ce5944d7a273edaa06990a79260ccefa898b8823967e53354a088a8211d558a866e28115ae84c3568d10f017c36b9e0d00695063248b8f311ae65e6aeb4b04b0876e1c55494fb6af558b50738c7cbc84563a77f772313c86c039e05881aee3c40b28ae748db396cf62d48cb818cb7a87a768234a4f14f93d0520f1ae2e49e4825d14b6c7659e65ec5bc2b58a748fe1c215d238a773476d32fd3c69b043f97086560b312a74b38e24e152eb0626da630b667b3f987e082f01520dc6a7354358c6be5f6087965e2917557e592f9c10bf529686a1a52c5c0e634336a918d9771f807caceb6863be276226854addd91e151f15cc70f296546db29bf5261166c11cfdbd166d8b08bc04210c355e74ee30a2a34e3502671880229c0aa5fe8ea9e93d19348a307c10be0a03c8bce0fcb003f6d141e8fed17d37cfbf6b1fc1794461053d48322d0de3706d339c4c59f934bc5c8910bd439e9a40c9d393006c59982eb86b9ff7da7525f5d36b83461022908974adbfc4b9d1309bbc5f51327521a44f7dc97278f20001303732d2d614707c837145479ea6b43ff5c500f59557c7a298852fa6ac2f123a6462e6e437bd8f1bdee22976a012d7d26b57228745a533d81f9c0a02a4e6765d5efb8fd20dc60090e17f127911eb6732177be74f13f4026cfbe8fc92532790b49562e90033ad61f21d4600fb180312df5fd5d029f844ba8771b089fe9ad7c54a3a67da1d467f01ba557329a7766479523244811f7ee2f4771fb85e91bfdee657da00c11f3708dda413034dee3fcf6a324ebda25d887c92a5cce79ef498cbea7a80d2d0e7c472ae717090b8eabfecca17dfd4492900b03c61cb69f6e6b85f70d606dd4a52199cacebe1389f7b3f5da6652d50813e4251fb1dff674f5c29e382943f53e99e17a154f3eaab0ee97a74aae408255654d6c209c1a4e6e993d05e63f34cdd0afd390510537f4bf63573a5d7113df71f94eef05e063d1544d3286326e839ac86f542ca7e973446c48e0b67352d5274456b18f96845de981c56e3a7265a226fd2e560616043277275397fca7943965ca348eef2ca50a315696de943c35606c959d215e5a144ab084bc1b55afa0f72bbe1d5d7663acb79453987d23a6ac3abd208cb625dae81952d7383347f2933930ba88241202d5be57c56d4d8e5a04a02f29f41419a887f0cd0c65e218726571ed1f427b6ebe2b1eb64c03bb0a39dafecfba79c7f12f7409b0c03ff718c2c392ece45f748f69943a249db59eacec1201e62d217527e43e52772db9fe5dc02696c805960ac89749c28d95029d4feaf29119357e8498e9c14b28020be422eebf9bed4561e6fc569fe45181fc018b9dbf46f47e90d76061d9b33fb66272b5ca79b4701c94efe556ab2f15f8422a6536a8aec02631ef3e2a189399ecadd193c527409213cecd32e62faea60e251d56d54ef6ac3e50e1337b94709e98e22135da3cd846ceea5bb011538b5571f63c2ee44c2d4d0161bbbbaf97651f85b5047e504b92607a54687314b84cf491e1ce0f26fe5020b32b24f56c5ecc2cb62bc264932576bae50f64843718182a3bf953265a81698e25ca006fae089e2edbc0fc6b6406d41168817ab9090daa9ae0a41fc8965624a38ab352134402121a0fdb7e6145c9cfe4582e6637f5216099221d6b19988ef1519435d0c22bfbf2903c9ea93ef457a94939edf655a420e021c0cbfdfb33718350d8c6813b634bd214598c6f1a7bfc691f1e0c75065c52822903a4626c76f4cced2f290386ae487345b3524ad796f3e51ac413550544dc93594884932dbf89449448996de9fe726ca9824f374fd699247dd88ad4b37a8cc5c20212f5c224753c2287289b1b0adbe258655f730e1aa47bec0ef866428f06de699e2a099c196c83bb2e53b248c92085ccc3c637f1784454c4dea80a1b273434047ddb78b53811c420c75193791d8ffb4074e45f679e8dc4120f378c8ac6b132113524416fd95f46ce33b108b61ee137a58f4773ac8a7303ccce4b7ca74f04f6cc63330f6f8b054e21844ff62cf7cf13342f898d629fce8de18465883e8f2dbd3f6cf4b129c3bb89106caa2d27a4627fd7e3c10d11bad09abe828c6201273a2a27d08852d792928fe7bd9d41bac0fa3cf9401900dbe648568ad5831fe37d20d21562eda2a04c519535b84b223951937fadc449fedd5dc2f300d5f688372ef3e1fc0de0d492b4ffeaf46f3f5c09a748c28a0b85105214a8a34e20e171d2929c896851f57d29a9fda70bf10ccbadedeeaba49e51668a166876502fffc5b38f240726a6e5eae53d1f6158d74804af021114b415aac1ededda9e58679851b95294ea0e2052a4848995e0934e9ca797a595be32e875c48e1518ba745ab52a1e0c535e6c66341753f5e92ac0266834d35e325c5f05c3f993a2dccda688995c5400ba2fb019a915a31e836d24c5dc0910d723f3c405c45949e6179b99889d2a30b793be0ae2071817eb9bb788bba9f6065e94386c2388455d0a1a37f48a53c3f35ba472c81976d29d7b4f51cb1dc7b28f1d96ad84a02c4bd68e3dc282321a51719a131f010ab0350acdfa1fa2c4d1fa7905483e2f24bd13666bf77c34a28f3f56574468251318e84511e1f904d24594141150d010a85f57f738816a93e8c844d35a10162ba94481528140c5f19d4098806d9a88ca22386ac5ac70e3cbc2c95c333c97666c980b693e6181d50b0aa51bbce7942b7b6054350c071fe04234abd91d4590df084067318265ebc3e35f879f611bd0d1661149ff45248fa27a1459fa2fd4708a9fd9d86792bd9c5b79a45e5bee0cba83c4873674e452617d909b6d46dce69e82bd9d80249210a2d8466729aeba3d3bc18d67102280f2c01b95e6453f8c2e90284cb2e1e4d7be1e61849e344cbbad9bf143b1b3152fd97378be14c6969a7c28ae8cfca4e3b3324b5739dd1889ce4db6b9e8ef3cfc654ec272a21a5bfe69edc974f1f4d0f69a52af7d3d6c6c2fdbb235006951ea650c3cfb93bed60732ac75db94403ccd1b3393a7e803c4025567d62d373162dfe2336c4ef606306826e40d4433c040501d0c1d9a5333301b23bd107d677ce49086e951b75152179e494c9fe21d12b2bc08dce4516a1a24d4c3c3cfb0abb33d852b31af985e7c75caa7ea95c5417abe91f301d306569b5c6c4078cf58786956aee29298613cb0fbfe1b886d90a2f13204a09f418b42f479b486583c15acaf32c2ad2e693d4cf8bd20a5e7208cd8b39696627c1887ce679709d92526458b07e521f333a406517fc0928739303362af68aa285f514796ac983a78c3385b02f87e0bbec9f15ffb90e27f553827c47e55c355125d4beb02825c64b9e5a48c37a36e33e1cae0752f46c2964a14fa808806843cff20dc38eafa99cda809f3bc533b9d37d0370141cc9e96ef283087434b0123cc64d849c174f3a213bf8173c45f843fb243ceb6b34edf9ccbafacaa7b80e78526b351db7b75a97432a749d70c950833b7fd0911525fde4e40c6e1be321c01ef35cf352f622d5d9909aac0d233bce39fbf5d3e0962f463f30573f8700fc55316812987194dccf00d8101d97e456a2341748ee2f6bbc95d4425ac89c2e71db2425e3971fef5f3016d1cba7ef78fed4410222e7931424e6e0be0e825c7560facbc1f9e95bec41824670dac4a5484f169246ba60430a45401ee9450d163bf15674689dbe25c65a21047994548e3619c9a7b7861298b3957da57315bc53da8fc2eebd7effb166f562d2a62f8fac01104116287007d236de453ac4cf105a903804d6682b0b33a93ba6f445061850ca087bb9e3f9c2aee0004a4d8dfdd27761ae2b3f75f76c4348ff83d2f944b8d2066a54fbc19e9087dec235143ad73eb880bc507ab6edf8085e4a8fd0646028b2010a2050cb2a12b15e8ca18a55a40aaf43d80413917527d2ad4ce7341bc327d2760aa354f2d643a7ff4bcf2cdc2859b7824cb6065a2e0a4ee92c7b63a9800774b9a42a3abcb8c8645d30edb53b415864b51ad11934628767bcc09306d79050cdfd5b16d7b3135fcfc57fb414e549a8e399dd6161cb68ba620b0dbfcb366c1aa41c5ca797fea7c74cf384c608db12aedff6c4231ad0af841f17044ed9d56780bc94e980f6875dcd76082b1db1baa921c69f715f90dc05f9ae5db60745556e1b604ece011b4a16bf4f3f6283dc5b8cfbe7e468a66e95ea011f3038f050653b8000df6b5d41dcceca801da188fa0ff3547685b29889679f65e1c85a73a0a759ce3bb5cd0d7f49c927ad3c25d1815efbf8070180cc62f5b956de29f235e4cb5106e5f9d7f9610d9bd60be4e0cd642bc6c46a13c436a3530ad25cc0c3befb7e0d5b3ec62afa99983e3caf0ceff850bb6551438604a6b8be0316bdd30cc5ea1e38fbc680a192d2bdc03cc6a7ba5e31afb94ce7c23f983d956fc3b1fe15e7a536e0e7a0b8f6d25bf800e220974d52b1c49113a8b2b28170098b3effd5c3b480bc058ca99b19c28c405c72db591fc310c0a7c58edee5d2d3333d57b63b07b6a22208e86c610c23e47412d95323e620acc0c5408f9f0587a11d12159a692595f3d43b0a662b5b1711644473268404e9a9d9985704528e1795b7eb4a988d4709adaf15a752f7ac8491fca2fd288885536843b12037759fa6f6117acee6434c5f51c8f3691af2360a98c3f7cd3388364923d9f38726c7ad6b167eee682162a17b67dc9a0edef85db28f1c6fb72a32481837218aeff07279c438fe3b8ca058c6b7803a84f3e9301c4a0e78446d2dd489c2c5e7ef52959e430aa5274d90ed659bd1a8d64abfe26bfa46a151a1454605541772a0342349c1b7a5683fa0d309b8dc0d860dd9b88c6c707e0c732885a9f3096c12fa14e5f112104100c515d336051d7e99cdd64485bebb51d88138eb88ae861dbdc49c208b4e44088bc6620f2502478ea385ec0568a690c65ac80f527a4c50a35933680b9743db46278063056266a02e3c807b009fc6da21f93140f5b2fa061703cc1596ee9b34cc0eecae1ebf0afd9ec846a29431a692fb3a3adcc5594e898aef68d32941ba4f983bca8910d8c40691953c549b8abef41482b348c035f9b1011797a84055b2c849b5d928b5abea79133e1a0640ade9d6f2bffaa2b85012630a29bb20c4ca13223ccbedb4e0476f4d8f07e5a131bc2c270b299cfee3155c4f2a6a3f93a6303c24cf51ae23d961ee016a0bbd6a91b3f3f00a539858d31717822074d47d2f3978b8acbdfcaa965a351b19d8eebc462beb405708ad958c4b98794d81f2c9f415d80fbc8ed06aeb0fc2d6b5b041d8eb2763a7c1be32da4312bc62f9553270d655b834bca3f18e1fd476a5f960f96729a45e22715ec81e0dea9e8a7d915d8d96f5246be6286f770388f39790460ce8ae2f06587dc265cd374bc32998089ee54632f50493a9a811cd9568b82f582fa9a5a068945ec73b644c3650457f2c851b220c456f3b7733e9e5c6e6dc69df13a740512f2313aa436c42f171901e04afd2e04123455cbaf13eb65a4f2a3d61dcc8793a005ccef388219a5e3c768a7b850356f108567842088daf672d9b56389e1ce014b5ce745d08f0ede096c55b9e7d1310d664f47b8bf3d28a2fe6b9e5b21d698ca97595618a0509d0ee0b386232a18f6a010ecca53bc68b66a1344433daaf061e57b109792236d9bf893ea93d740198c9cc85f87f50c56bb057e486c8d0edda5878014e0cf6ad312ec0c0fbd1b2c64ec749d60cb7d7fd0d6cf315c46573aac3f1244ef53bd36960d4f626412bc26cf29a24285e311b8c4c4aaf61a6411bc3c6577703f106142d74dd226f52a0c627b0d91227cbe1f16176421d5a06b8268bc4e8de7a23089c893aea7322f5e0f459d583d3fb1aafc2e79409029386489b0478bcb9ca689bfdf75f9331dc5ccabc8c562206f5242915911bf2412ef2827361530719fa4abe54d6fa2461a1181fe377a46674bea74fa6079069f37fcbd665937c0a3edf6e9aac8771aae59fc25b0863f98b14417ee20e82a514cc7690a6c250ac6a667c96b49039f972c4431e50cd80de703835301dbc1c74142718bf4c01e94ba1ce236314893a3427ff1205a3a0f7fd013f2c1df8e116a1aea15217733301d3e8cbc1b128c7b1a6a6deecfc54f0a7efb12fb798f2cfb7d3f4364eabba0f81120a25d834657c45f43fb4b6276e6d6f22aa8b862ab1175c78fc0e7c16b7e3946c28b6f861a720b5ab6ef5cd50e6ef53665614f38a54cf39d56352545822b036e33133331a4feb4c84aba1b31caa264246610bfb6945aa2e75e2e68a7a5a76125c341cadf8c89431045089ca3f81ce71d21bb4533418741917952f9ade3b3d42f4523dfc558c2154fa19e96b138a09984098993b966f9fa2fb287ba4e82e549f2928b9a45e65974aaf3170bda392bb204c420cc6133c2566c5fd5206fe2c89232307948b48daf2c727056eb1fae5dfed52927450cc409811d053e7845545a7db75b968433826d68499e8229b62b8090fbdcaad8051f60bdff2e2b31b947b9b4de90560d64343c4f0e9415111e3721b8b659114896e1693a7238d8462a422dffe8da2de33b247523d53e841cdc78a18c4e378c04f35f51cc6811f76a160fb5e89563bca7329dd885435aabcaca00654e48326c79e0f979235423be02c240b464804f16c5879786768f4291f746d790d899e4a7f601c1fa48c0664e9cc30c0638f23d5f428f77719d415d8e480ad5f5b3a72779ce2af372d091bb18a416294f27a80e04a925fc6e35e15535adee4f8c7ca6e090395cf9ac1639b781afd5720599e29a99533414b205eb1e4daf9cece8a1ac5832069e304c01f554d76b54f2ee4e847bbec51090bb3a25b627d0fde12826502b3f011cfa08eb8aa8a79ce65474b32208151b359171d6fd116a920435c4173278bde1b56e336f08c0200032eb7a1374a5a7241d4bda99c501e378014804216c4e132d9341e064daaeeac63b9fbe08a0c0e56ce4ecbf8846ac6a6bb2090a8bd87228a6b2f8c49cec6c0cac9a2ce559ce655c541b179a2575e9e7de18151383dcca5479b350c77b67b8d049575a673e3bcaf29fcd914d80dcdf564b21486a06d512481b982626d3a5d838d9657417051aa848e7d24f445d6db7740688e2eb67899f4e8e1e5a12731b8726e8b87d3af23c05231ab4591d85c7d6ea8eea117a9f81fa860c3b930df4e1466aebeb967422e5f5c559a4db4b4fed24a04065ee85f6a8c9f48b7c6e81297174a2a9c54e3f9081fefce5b3e411262dfe55f4ec14566a19bdd29594735f54f043ca001d2c3f3580aead21a268b43356e82c99f1d9fbd7ecc7b94f9c00c107f48433dd298fd0e33e858c765be42306020ed50e04cc9176c57ba475f30ecf3e39177e45f611d113b440104a5cf395d9452b4a3acdcdf6ee1b354dcc85415160586bdd978b5d533062df998ddc8c2356b87769ab04bd8d7a75de4cec368911449289248dcb5faa8d0440ac665649021cc04fdc64c8d0520834c99918783ccc28270a3b28b29bac26d53e037ce85fc3ee53b918e2d2bd3bfb06a274acca55010faceb9671d33ad0bafda7da2b0f121e42124f218a633ad9678653c648818822573bc6e3a2990fca41f24198f5d514692cabeca9ecf9d8664032fda4771a58a7efd7041bb634b6f18d8d44ddb8de25a1fccee791d21369e4b52e3889f8d15dc7a6c0347feb002ecb744ee8906dfef77a91466e5182a8cb42e4035b4de044a142c0b081508aee3db1974b08dc3781b31cc6d5b215eca31705b8f22d4f51f69299195395de68567e148c0b152439e69239a0b312a9f4f6d4c62879ad919b6ac200413c40b2cfc7eb266363e5ecca346241f89efb828430b21559644c58f1649722f58c6785b2d4897c4f9d33e4f22ef866f3c7c56d384300c204df76735515a0f0e3b83ffc2542d3c3781a250a26ce579d2ea560b1da309d0bf609c60bfeef3a41143650203198a114655cb690b1482c8939f897a8010c1a7eeb4bc52f6172d6591abdb1c034ae5292dc739170f1ac3fbe8f7089fbef40d744edcb43cdab725a50fa85fa57a07302ccd77d0c41ea9ae602d376bff0e8525ca909d7e66e931ae797147c3e3e7a68551ff5d34146b314a7850e0fb904514c0f4c2b0ad70261a4f096dad7928e7a852b0a21e3f85cfb814bbc9c7cd918dc341e8108d0b56787bf8dc09c4e727755e74bb716db371018c432c789274d1e3a96adc69a14cd2a13740b0ed0db66c2384df46a6151146e0b1c623d433dc96381571390d920855ea8e1476d3fe6ab9ca67d8f6133e2bc479042fd23236c429fac68ca38ffa5ebbe0cb5e5985bbe9d2251c4da7a1ec93666bcd929819dc173344d509471203a683b0ae99064127d848cf5ebdffb9c5d1ada907901608d33939ff93ee35183a3ec9fdae2883b2924368714de99f64f4eab07a9fdac7f88566a3265ea79e6cb87be44d6075134ae0050e6cb8a5a9ef8daa075a7906ec846052b6ba315ed5ba2d3b309d5e9e9c4ea22acee663cfb2501f71ef1a66522b0c4b30c1ea52596c71e0a2090d8e6ba59eeccd8f7c7f130d66b940e9e6e2cd8854346c1ea31b4355b66b9e7287ea9b6572618c23ffed0a26c195e3c3a4b670ef6799be442de89f6b103c7bab5ef840be2e39e8de12402280f630b4e8c3bd1b26993b0807e78e50f900997a3e940ffdc47082698a9e6a0ee2358607964e4a0d6eb77f07a9ac306c11a2b447d955b0394fadd8d5d9174991c6a964df980f5fbc7a46bb43a93a761908baa3b5ad6fd6394ef2c158282a4d3b559a9cab0387d49c4a3ba60cbff8d495d663eba70f344a0ba9454a1c14023e8b1c9fc03be2f0cc00821dc5e9e2dc07a21dfcd6a2e8d033fd3034c3ea08dd865254fc6c39a5690820a170eaa64468f15077f57d94ba5b6da43da13f400c27b68387f9714f525f968f769b48065ddce82675ebd65e5885e8a454f35b6a6d786324ac710119409034171e4648f7b71450f575124bb3b4ee542aa5aeea48adad1ce0a2cce5cc3bc48a402bb71367e96c4b51ce4608d5c96ec4a4b91de0d4e688b244515e89e8d66a5a96d39589a4a556f809aad6fd72abfaac3d00eecb3e014c3c80d2c76c85844d66634ed39be5047c657c2282273185930f679316b7f1254b49833beb750114fd038ab003bd5920181aa7036d6f925dbc53591638e7aac7703093e1a5afacb75b9f6b004c61aa54cc9abcaff4fc48f48c1e5753a2e90fc785edbf6af3eb558a509d101958655fa41d664c11c4f692b96403fad88484b4bd6537b9f796322519a90b5c0bc40b2b19f1f301ee37cd103f2322e0fe18f19b02f7c3885f0d37618b134f2401f7a7c4af0adc568a8480c517449e3d709d84ee45e887c135bcd5dd737f6972789a276ad4a0b9027bde7bbb3c90bd917b1799745e395983a67f90d7c7fefabe5a7dc9a4c9d50df0cac76fd69892c025d95ee47ef287bf8414994166ce37d4491010640cd5caf0b91911f31f9080ca174e087a8225e6eeeeb0a7753d3993de50191aa77fbe001a87d69f4f43e3703f3f49e3743fbf062536344ee9e7dfd038dfcf5740e3dc9fbfa471c09fcf80c6097f521846fdfc4f0beef747899f16d886fb43f19302f783e287050603f75ff12b03f77f6249fc9680fbadf87d81fb3df123c2fd9df89181fb4de27784fbabf84581fba9f821e12cf8417720cc9a68888a21a2a02a66403f7e0b0a8ac244500ba4bc404806546a60038c1a020aa2df4946433f349e5923f5d169c8343414e4b71185c2d8b7f8916ff19b6ff1231fbfd57c6ab55a6d56abd56a7ee437bff1f8ad96698d7695f35aaa86aae19a4f8dc2dc0b3f02918f61732718f90dca8fdf7c80424f1551215b0e6dcb1c2705e6385b77cd49ff8e0a4cbf6b7f8ef7970553a120eea429694d48b502dce592ce28d7414352a238ac879a8823a22dfadc0e1aa79ffbae354ef7dcf7138de33df76da571ec73df581aa7f4dc3751e37ccf7d4bd138f7b9ef2c8d033ef73dc50c529ffba66206099ffbe6493d37de580136faa1b023b445df0205f910857d1285e1405b46462b1b51a6b5150df3e4a37f23a31f5c360f51a68f6a022ee98c27d331080bf6bf008a04d8c4c23f3327b2f0e8b72d9e4d356c1a514e60ff30bc924add405074c2bd46f8be27944a5cec97e47d57a0449438635f18b35d2593ce337f5db091997f066edc81521e27e9b7056e0e87a3231c8ee611f7f5e092523932bb388289b4580b9b318b3385511edaa28fedea9f0695af4367b4455159e086f9ee2bcce837daa2afeac6d2da541d4bcc8de58a527192de288cfa305bf487384222d3a74924654a676e4201a655e89093f4515150a9993c86e8de093ae9dd1e68bfbbbbbb45efaded0cdab102606e8eebb10473db07fde53fe70a4f18a62d265cf63c1b60cfbb5fc9fe0c09509f7c6c7a0ba5cd2568643a599f7e0e20c5b3c641f96f2c3f7f0f9adec95fe125a6b7261124c2a6115c426198b6bcb33f293e8de52ac38078cbbf7b1d19fc9f5e21429cf4b675dbba6d8d1bd516a6fab68d650e9328f7df3829a407b8bf0492dd49ff1e85b4cd57f8941a4b8b1a4b8c475b78c305f83b4888c2a6358d25c63d962bd759425bfea611e022fe32bdff117f9d24fefaecb7ec7f83b5189bfa4ddfa1cc84041d24c10b1f8801041354106b1fc6e8c112355081172e62c4babb8108f93e1cbf07f1fcfee2ecf5c95cceb6dd4a265ca6dd351acabf1634cde79ac38f4cdf769c51925c7572fd1d3c90bdafd6ebc41d3a7ba58f03abd89df0fbcd4da62589fa72f53434a1c649293e893921c7713f29ec9f0b61e8fe96befb4c43d947e3a47dcf6112e5d27b188309cad018b9f49703bfd2483a6947d249b722e9a47be30f277f804b1b7f1b2037b472b57ee52612e7e4ba275424042114c170ae75a4f5cb19845c6bbbb4d0e24e1f3804cafd368b25b2b68a937d458c2e59e6fdff9bc637107a6835cf40c1338aa22a7966d07685cb2c93795e41c20efe7a9daa4e4eef5b87f9ce9ded0ae00c6f4fea3b94957a9c2cfdccff4a33f28462c708eb91c016be8bb79464cae14bfcd72496924a5daec0fe26d0e83b82099bbe6ffe12e7d897ab4cc94cbffc4c3f266671d2161d89884f5bfe43c40e61b87f4abd4a34fd15f16bccc1269bfe15c610d30220b4209bbe43964eced33063395f4369768788433e4724df26d34ffd90cfc91179207224fb0ff99ccfa1a67711e718e96c7a93692cf994684ec67829cd6c59503fc3981930252d25231f9a93cd1292f255f9866cdcb06103070e1c3f43190e222b45b69b162e474ef697f47571b277664eb6f8a2388ee3cf50367e8ed3e4a0a1d1a143870e283f4e368fa028546a557ab4b210fdf06154e464a7f03394d56cb939d95cacc0a59e1cc65ce2e2a49776a88071df9249f63b5600cc3e4e00bfeb6708037ed7e7f85d81853f8d65e7d3bb58e3f5a90b1a15657fcf0738f5333f4e9cd39b684e6e2202c2899b12e77bbe0e3a1dcb9971f53be00c7bdcd5fcbffa994a8d3bcc68c0336ccc712742df5f6c82c85f2cbec4e9247e2262f51676fd4be461369bb91ecb8865e75923f3b7411911c8cfd0089097f91cafd1c2cfd0480bef39876271c7cb88367f12676e412c8188a5bfcdcf50c6c2c84292fafba9bf230b893f7d1fbf281f15277bc7af207e2a88b7c7c97e1bf173a57ec7afbc5c14387d7a4319f58fca2774042e4b3f44e012ac29814b904a12b8048366b804a1f4e012a47d0097e00e145cdea42b026c827118989ff1fe170c650ef339ee504a403636b5dab44d5bf8ddbe2dab26e0dbc54992ca5095799bb789349b9982a408d1680dd4402dd442a7bb7379a00045e95ad7dad6b7bee1222390e6e3af999f469a291b496636a3f5cfc8a042d9cc49fc92bc803b200fd8739974be7f02caad2abd5f98de4209e6d26b9909afe59679174f2a7c53a1ecd419cb881f17cf33a2e79be4059ca9ee3dba5b9cec9f02880b172e485deeabfec2843295bbbd56c4dbeda6c55654a58b9a95222d4243425d00051169f9014d0c28516e5b8e6a5248a1a6e6636adce6b79d9923f9cd6d7e73a459cdbb98c28c5096424dad26859af15a2932d2e2e3c70f1f3e5ec6074d08885a1102a20109d19a8f77f1c72a94fdf0215e1f3f449bddc778872e51167fcd7cffb5f9cbbfa552593cf38ebfc758d6dce3fd6bf9d4fa1cc7ad9f096533ad19de5bdadc8436999264c813f9265392c4deeb8eb7d043c6261489b6fa6323bd0df9215f525bfe792b2952cecfd6bbd8e3b3c8f331007c8e08840e459e4628eb711a99d4dc6afd6d7046b4b98768b3b78a884f3e0680bfd78abf4a7fd5ada9c46bc5ab78152b43fe2a4f8fbf2f953b14047cfa99cb2b944f7f859cbc432a91e543be7e77afeaaa542ad53d7db5ee5df47e8634a82ad0cd4f242727e756a04acb634e4e0eedce54a1192239393f696be6cb2a34f357dca1ff5e4b6466c44488cc3c1171c6547746bc35274fd78a93fdd5566db59a0aab54aa2febed8e97ca5571b37bb95945aa48aad6d34b391afd92a3b5e8dfd6cf50d6fa1c9fa1ce10f2a978839cec6f89574a157f91352bfe22fd793cfdcb0a65b405d266212a43f4f1df1aa10cab6e94207f49f1d7e9ef5f1bd46db699070f9224c99926332ff3a58f3c5463e959f538df264d5ce6a938f32ef27895383326c9cf715567f26f84321e2439b2909cdeff3423de5801be4f5ff517472853ddd3788164c4bb235e9ecb43b4f9675a4112a3e7fe70429c1007c40161aec6d5303e9d30c67f122f94f102cdf8802f8f93647fea777c797f52e3f5d9315e5a1078c88f2fbe8ea7f9f2f6e4fe3b44ec61c8e321620f34cc1891ef81c84f0787883b4f3e96f33c1f23f2331c22e250a6b393f3447e860670121bf244442e47dc610177e36c4c40c0031323f24f7ec4888c3d10f920888cdf14d8a463a41973d070a904e42f5599039871bc8db7f1fde0ab64426ffe9ad95ff5a14c85f1a8721c4fc7af267e55564f18c5112c7a11b4c9a8646466666666665e06bfbbcf76ddbfa7a7230bc9cc3b164b269e231cc0cc7bea943aa5c62002747467b3bb7379548fdfffaaacea6deef0359b71489343523dc6187fd9d1701360c68630a1f98455e2c765c8cda84b972fc95ff7fb3f2ffef7ef10b103a2adee8072cb7486f8abc650764fa7d3181af13ff969fcb2107d5c9c6c1c6fe3cbef28f7bf0014c6a7a50b2ddfcd5fa9efffb6f80bc7e3783c963de453f774875cfc363ec767a6096529c781e36f83291c236874c386081639b9aab152ad646867991c9ff9017f46454e0aa98551b3cdece019fdf225e65faa41bba2c0a69f99e1af56e5575f7e56661e8f2598f1bf50d7dcd64e9f4ad9e88d14e019b3b495fab2b39d52b3001dc34c6abe9f52e92cd1217b937c9353a79488b1b8fa19995f3d685463258245a799d349a552ada280e7953090f2bc0285955cba6929b2fd09a7b0cc951177e8ecaa53ed88122defe352434efa83e3a782d2588ea5fdc612e7bed13aba0f8dc2da8bd9f23f821d23fb3b21fbd3bc697d9b350ec57f9cfc7aaa18c3e28c95c4924967fb9e155b4b1b895de4a4bbbb8f1d9835fe54e4a15dfe5ab41869b94151911627bdf6ac5d5ec7fe61d678dfb838cc7d3ac9bfbdd43ff2e97263051ce795aba343f9b1f964ff2e7221a15a15f71efa1486732049ff5bc5eec901ae5ff64ef61d277b74f0e83c8e6a022efbe6404b984959a8b9e7b81432477f455bdc731cc7711cf7978afd5306a571cf0145e942450b0e860958c849ae815849bc89bf9398a52dee5b9c5c78971e6c619bc52e0ffebe6ca1a6f299f3daeb6f8ee3b87295b97226711cc7dd64eecb69a464b5c2d8c8e77e1e3af179e81b0a7b61a716e33e87c2668ce3fe36c7719cffd7755ed7795dc7719c8736f87fe31416ee1af74dc47d0b71df431ccbeb3cefc67135378ee3388efbd64294b98e71ef753c34e156dc2a73d673d295bcd2683d173ac1603ca58480dddfce203db273efef3fe6fe1e9bb4ff6a564a6fdf0f30751fba3b74d6c0ac65182fb87f7eff8cdd495336ac045b22267105b48e921984cbf56db8a171bca7117a86d19309b9baf86a57fd1f1d25fe2a5fd3966449919bfa42fc55b3962db9d2ce08f8d3028b529a60029b9ed44f524b4f8d5052ff06d7a99fa4969ebeffab717cecbac02624754affa5ee71414ba29250270b5cfa127f5d117069fcb4c0a67659204cbf56d1096c6a57758eab333455af3730d171185753e2001c68be91eb3729aa9cbf4a9390fa476aaddd498b1c94ce49299d732c7b96e99c5f452438cfa77496587ccf209d7b802f7d21393ec0e50da6ad8e42a61e681c9a29924478b59a1cadcf79f386b628a5377472e20d154471410bf0122c0882410c80ae0df098fb6f1aa76c6b57fd37b3456f32fdda45bf8a37da45595c0e1c57e406fa370eeb6ef47364d0a1af6489bf6664fa0e10caf471d0813ef7e00dfa23532099de689c3a52fa5fe3ccb8fe84e8ac6790ce3cb487e6836e5be3f8173969e46a1eeaa828f07cf7c66ecc01872a025cbadbfcdde8db02cf118776d5aa38599f8a100f6dd5afef4976f56ed3298d9dd4484ed6d24f520a2823608289208c20828a230a73d293842ec428d2820929bab019dde4dce4faa82ba217896ee4e4b41dd1e102131181cba64d0a63d530b294c496c4ea38bf0fe0b26d3617b251d89cd3366dd3c623e43cde22f2578791eb7796afdf54babeb7cca78f4e4351d134239fcd53b6ab7e0f308a0853980eb455df8edec8334e241e1fcbd52a77bb4d070af32190484aa26729681e1d92663c3c46454e565414d86d220eedaaaeaecff2f95ec55fa5bbbb8fc376fc36af1cc196612e4483ed7c40473042aeef41feaa79061530b60cf9abd67cb6cd5fe50423d7afe5e452545d4bae3f4bdf926bcd0187c699384d679caf9ab8f4af8cd125b790e79531aae4dbe09d012e47d7d8ae7e1b95313715e9d3c688b26347e3582ef0b7cbb2a603fcd5730a715892a3dc6f73e3af39613b9f10d24eeed751e22f99dc7f83bfe615a45bee5f82c37cfd181b878e2c18e8972fb97beffb691a877ee3ccef2fb16278fa9cc842f2bdfdef2dfd0e9380df4338c3f725fb255613a69fb1f02da6ad1b940f1ac80cf46fe697369ef81501cfbfe214964d4eff119b9ba6f33fff7eb8f42f1565375c808b38e9e26c792bbb0ad95dc8ee3f6b5c86234e4e2bd9eb73244d11dd4fbf7677a55f2b455dc1f3294c8df1ebc1ee51d48ea34de4bd7dd7e279576e378050646dac7ec97a47feaa36c95fb57c9eea3f0ed449e852b6971d2eb9f41e1f24e4d2a17814234d44611d343ba8892811524dc281c272e6120af35f3d462a3de8ab1b91d67a5050b6636933c599721c27c5c90e72b27e4559d6577ad74264bfcfbe07396cd6682e44a5be57f15a7dcf52df6d5ee4af79c5084aaeef467e73188dd5ff4a96c85f65f3e4fab5d6ef2cfe6a308e508f72d95a72ed235bff087c6e3ce2be7eaea1b520c6779cb435613d2ee1965098b360b6eadf60223991eb2f9973063552fdcf08cc556e6ca2bffad77724cf4d26b8d4967d8b59e34f7bb2f7d39abf4ad792fd9df638ec0520eaf3e34f81fc15c59f4af11797fd69107eb23fa54287b038cafeb48abfbaec4fad38ac8bf9d39eaea5670b8dcb8f7b19a23bfe2a7d8becefef377ff517d9dfbbf8abdf08b9f423244f2a422ee916f72d6690215a7322b751188c48430fdc278935e14acdc3755f3f09eabbbff74d6313ddb149b59fc4f4f539eeef27513df7e0cf8c0763be7bd4d8a4f4a84f8d6d445b8e1a9b744decd8a4c9bd4092fae027b15fdf348d526ffad37884b6a62441fde95541ddab7ec6d83f3149dcc38c4d54a695e9ef7fa3a5ad2949be07df8e4dec9f3e352aa12d7fd4d864a83e1e9b9cc626e0d8e48e4dc8fbe09bc6d59424df87631390a42dff6fe481b6fced8803eda10eea24daf22f8d7d445b7e435bfe53ec4c6474b44a6a9efee920dc4393681acd233b93b89f313b6376c6acff50585d79b8b34214c68d3b3b3b3c37b1fa3c64acfe0ecf2a561f8853928dd5dfd9796263409c9266b3550c08cfcbce133206048dc22c56ffc94dac8e73b668ee43554edcc3225853efa0edb7d00c528766101c582c788a6c7f766391edbb686a971d41bbec07e1a787ecdb2d6a3466e795a2a06c6bae62454a1335c0e2657a81c1efde9681edcfec53e479e58aa14cb39db1d7bc7285966cbfa9f8abbb61f0bbef37b5cbf6d8755186ab8c8f0b6c6a97c705ee3a21601e7c3d6696b3a88b16dc7d7d986e0b5cda745860fbb80be6de86c2c2b77f43613366ed87e277745d15eb813ee82a1be883e3140b84737c86a820e0eec71c68feb628018584083d10bf31b0a95db65bb96ca11e4dedb2d3bd3b7787b590b7ec5b5ce21cb96c21db5fb650b6cf2a95b34b974afd5dc5613bf4562baa65fb2de43016d26c36f32166bf87ec05b2828b6cbf6dfea2d9dec8765eb1e2275b2edbef2387d598fd92376ee2afd294eddbe7c1766fbdb75b58225822588aadb5fd954aa52fdd52e9963ec7bf92a971fced4f1a3b4ec085525ddc49e37f84c25835d4f7af23ab7fc6eacf98159fb62849fa68435bf41d467a8bfe4da67d734966982f7866cf767abb77718d789e3fbfddef1cdd94042e6bc60ea3f1567f0f21f77fd3397f3849daf0fc92263b71274d493df039ae9df3d1d27a8303398f758c9564feea8580b3a85ee4ea47c8d5c7c8f5fd061336a7d366909aebfbcf0c02cbf53d89b9aa22578f225777c2a600b3607a0c72fd3b025cfadb3be082671613c25408987e3f6a0b765abbb838ad71bc5d75b62b09d82f17b8eca14721a187a8b518af56e5a3c2c09d7f813bf75326dd5b9cacdcd847ef2476051014dbaefa9cf8b5abbe89085c3aad8ffa88ebef2d7d7403dcb73e72b2a6da55db45044625e1be39ecb347b2927c4d5c417862863ac2ff636daddd7362e73b6293293d7acf77a2f33889aa02d79f19898b935b6eded3b35a7ddf64ee771e27bd2707b8777a9cac3e445bb5b434d30970d9375417ecbee33dfe9a23c0dfd3bf55b0ff277ed8c48ab676b09a5dc0606716bb7f8dc037c0fe2df66857fd1992edaa9fe3d7095cbfffa2707580fdaf78e381f5a7491ff104cb2cf67d87b2266e0dfb5f1360fa4ea3301e68abbeb518af7280fded12985503d8c37d70b423c0337645fff1c02503a9d659dcee472e9d160ea0cb395e70121828e099a74d0277d819611aca38272050ae3fb9d6546e1ad23063f767a8f3fd0c6bb8234be728a684b6eaf7f08dac26eecf98890b5bed968838d056fd923885c5fdccdd2784cb999439510791896909fcd981ea7b507da7d5f79ffa5d7ddf71b2de28cc9198adfa491ca520d7ca825ceb8dc266ec082989e65d77da69fe53ffebc15e9f075ae74c1a4be742239d3b279cedba917b3bc3ee674d3ba171b2298d731a5edc39e3049e51248196a34802502eabb5d346b9169841fce709a352affaf92485c1c4fc8c9f9f436132bffa999fafc4861b14b084010e80000e650e8d63faf9159841eecf77f1eb02f7cf889f18b83f2a484265abf88196b25440c10814bdc20450ba10d462f4f115462138554104024bdf646d5944103a152c100168adc88108ec10b718fd7bb98021b84a407909c210ee169c154828e2b339f1011f310e98e9974219f5cb84122061c4112800a120e126014cc1ad22e54317b709299eee27af38418c5ca7a8360ceec125306785cd8f62f43d5b0948d0d9a122387bc50d8aa8f68a11508c44156164ee8b08e138538b4fa6efde43208292cdd1a73018dc192f49e2b0ff69f18ac332c595cc7d1113c4b89741670467ee58ad4f611c86480bee5c81973cb14401ca73fee84159d8943d9bf2ed1e4f3034dc5f9ea6ad01f56bac30e89a35fd9fe33ad366e39a354ab8722f21f78318a6c6936ea98bba2bb70be903578977dddde66606e94c3f48e374acea805a97084962e3b0c9c556e4888e122354c9f471a8dedd0fea7f29d582dd6d6c6cdad28ee3aac771966b1c17ea807670f56f9edb38a10fa813307deeab91f1fb02cf87817b2a76fe3bc3ee76a2911de8972faece33d4e77e8a33b39010e1f99c13ef0edcf6faf341d07e09dad048a9df7bae248eb35d5d37cea75fbf8a367c55e0f93d78ef1df33ec7a937ee007ab3032b6ea24bc14c45fb15e11e311138837b011bb8efd7e11ed3166dfda831613b9486050554603a6640763e1b961300d13c931d0b50169afd1d50e9cbed3b999d1b3f2b78be23d1a0f305ab90950d6c85f4343f805c39b265ff22feb4672b217bf540ee9f62cf76756e1adc4963ea6ee3b40fb3c6fb1c0abb63ef8c93c84fb9ec594d2e7b56eb6cc6f34d44e374e3d05fa15dde7719dc0c4c2b649e2d7a1ce631ef7bc6257b34532fb843dd30fd66e22defbd164dedf29c94de3d01cf7675e95bac4eb290f433f138eff18c32708f429cf4be34de38e9ad927009247b7f39d06227e02e095b27eb4c1370374ee95fa171e8d83fb4cb7bafc78e09343a327057b2c12a81bf2c30fd1cefc2ae0bee100653bb4cedf23ec7bde5f5f43c264eea5058fb305bde0f619444f6be52aa436133e679d8b359e3bd0905b88993def7c883935e9d39e97d8e77f37c3d985e96f5aced8992c5cc61530aa1e6f9f1be81dafb260250f6be857ae8ad8e2989f74b70d892bd9fe5e4923dcff3ba64ad7fd65afb395eb23e8b99d1f95326401f0374cc008e3c1f8c2b46c872ea7eba4f509e3f29e532670b2134e79cef73469e57bed892679e5e6c3ef520a472260dcd6a34221edb8fd1fcd95a50a962254b9e5a4c2d6605aa81eb46560dddafba9145c38c61da9aef243663935583d743f7decc7e2dfd8c55fb39de3f14561a593add7bac63d38e2c3204b5980d6d71414e61dd64214e2e71d38dac193b52e346968e129b0e145647d61227e7db2862053e40b3180e262ef0cf951356400106575c690217b139bd20c1c5124a10020f0d8c88cdef1ba5999d93eab68a3b694ab773de80d27c0846740c0a04f6b7b676d4b41260ef9bd62e7f2b057b578836042131b04fc1dedf21b0f75e67f196d8352bdef2bf1eca74bc872103970d4461ded844b4e535cfa3f928e133049fb16b3348259a41a40041bbfcbb252a08b6f0c24be582fb2fe7236c10fcf888b35da50772c9a4b305830221e5dee2d3549cf90405dc352ac4862224250a50bbfca1900e857ed93e3e0d2509cb0e718bd9f77ec672dcc485b5e2e7056e1aedf2f7c429bd6a9737d013f87b82dbc759df2e7f308fedf267d19fb93e1549f27619dd119508080804413e591ce65ab27f5b699fc66997624f801b048d6393c05e69d7c21838182fd87bef8fbcd6cee2df3587cd1bd26ca3ec33b2cf2b5e0c65ef0ceb987fa50d14c57928cc1bbb31b087324a6bcd5beeb86b4a58355856fd19f3de8e5358f4634764394ed67494d89cb8000ca323e06008a1242b66175b54f939010dae38a2054b9cf49acdbf8dfcbbe6df44fe3838e5bed25a29e5aa0c15a38f3efae8a3eb0c200833c860a326430c4840722494d1234694202b12ca68110010e59c1c225fbc2044088ecd859b50466f604fb400249451202c1441051b1b5719353f4c2be87ef8f88e5a6428a3240f25a2d041a3c4a5c9d1e287059eb58a8f03c78d272eb8e1c449cc468d50468570428eee188b869727143f6011c3838707cfcc2a94d19ab73c0495815405195e2c01464c66461056c018985046af1ff9912a95ca138a20ae28d12f5f50389451549f4ca18ccaccdaac856028a33388f599df2f9451bc6a1c6f22fbd3689cf9251bcaa829fb9b1a8755bf3315bf76bd3fb992d17eae8632dad4398c1b67667f8cf167c734f8dc0b7b6f3999fda72da1bc29cde5b4624301fd6b44fb6c06a999bef3cc205ca6ef44746f4770022604d0e28e76d1d7c10e1eda2d88ce7c9bc033b711786af1f986b0d32e1dca74a0e24e65a806418de35fa62ef8d3875973fba15be5974bef1b0ca6dca3cf4a809b4ae30cd560c893c0fe6dc3fd2f0bdc63b92497dd33e1729fbee0efe8119932e1c149fa980c5c7697fe5295e7a3264e676917edbc135d68d6508f01383a106dcd20d329b2348e4d87c6e9d153305df4e91299526a431a3ce633ff8101ccedc595c06577c9f4af5843e0b31d284d33ca347bcfbc753f61e07708260c1c7dc75ba5f794e0e53e4bf4f8b2d081c27c08efc2c0dd0ba071ba9f35a1655025e479450b9e4cf39c22f378cfbc72dbf197e72dfc7bff9905d038fdf4652613388b7b91c58df7c191d544f833063e28e2f6e224759276993d7b7615fd07da39d9b9a293d80c7d6632012e27920e14a6e34848d3861203cf5cce327afcb8c053babfd7041845043c63ac3aeb664e529ed1774a9fcd3c73b777fba5a0cf3cb353f117f5bc1ea02859cc1c76e3e3af1f87ed7c59d420d32e80327d17f221af6b9a0ffdfe8192294969833d3bdb3ff316a593ab33a7d1f71ffa3ea3ef3cf47734cecc9ed3b0e2e3e8402ad854eb4a01cc130a2050902b8d95c4f4e1876393709ab8e88ff9c1346f3ae1a873c4c90ebf246b7ed8a8f9e8d12279ec40294107cd29093946f17db0e1b861a38669052c1a332b99193130405c51a54e48a0f0cecec9f46428048300e27e3b514ab69427143ee0c0eb2614420cc1d52e4f2882e0926b7dea4fbe74cda2430e4b9cec6b7b87e664ffb48f939e0f70e92d4ff73881bd9ffddf3b0cf5e968023fdb7d0fbe874868bed71b67663adb45a90882e38d15e0ef3bfb8d4c3c973e86fef2e5cb973189674f3cd2aefe4ed471728e32cc5c6405160e70b297a08080e9cfec24cfff410844479ce4d2c9f8ffee6d059e3f699a08096e07821304ffcec609bf5cc1f4658f3c7f6c5109859dc620ddeade343e91bbb73f1b271c67bbca2945eede4bb50097be6b32ab0882128298071c0a463153931d5d624fbc98695ce22d743f95c0e5343262ca3edecc9aee5b7461d6741fc344a7c37ef8e17ba3f7f673688eb3fa739cec7e664f9c3914593dde38d9bd906c82c2c85e81cb6974eb3cca5ddfe4cebba13004cc56f75ed48e90bb1f2377375d376b3ad2c9eebd91c6c9ee97dce0648773bf7befd7f7dd77fd2a7df75fc36e68abebbe4c4d2cb97b23e0df509812daeade5a27391ca793e048e3e45542614f5bdd77d6944b53f7179c650ab9fb495ed1dbd5af84763fe72c42eebabf14fcc46a9dec494d5e673bf6acf1c6396b7a7685fe6d4ef6379e46208ec25aec64d32fa71185e586cd9a2e61b9ff473f4c0d1b1a87f50cdbe813846e1227fd88931e8159e37f737278a0b0ee9978997b21394e60eecb9b9b9ca3a0cefb1ce73c4a2762da72291ee464d93c4151b27f0759ebdf3c14666de7727e912360eac1e5fc22fbdfe0af1623fb173922839225d662ecaf50c63d6162892c547ce9810a6eb1faab09bb013a42055e7c80021060c4eacf6967cd59cdd5ca3ae9eddce903d7772c369992043f6a6c827a3c2ec19ffa469ab71ba0181698fe8c85e1efc1bac88fe4dcb0c401feb2bfba81d14e73ce096b9a8ec3fac6a47158a6f0a797f56fe752692c6b2e3d58bbaed6d2bda76359ff566bdf7e0d65779cf6466127daeaa0c0a99fb1194b8d4d56b4e5bf33f38e62a927698b0537b4e5ef1d987db1d43f79c5525fdee02f570420c38b2590f00414b6596cc6505fdafc7d1f0d6577ec2df7fbe80aa6576c19b4cb9f0b8d98720bcd1af7fc71a20e57bcc08722c2371025ced8f59086194bfd0c654da83ec7eb11daf24f893a5c542222fe2a69977f28da50843b7807d873ffbd01af0b609d7901f65c5abb03ecc20decd50210165650c1c69542cd0f1f3d5a248f1d3a68728ce2e3b861a3068bc6cc4a66464c10d38cda8eac2347c338d8608bee4e2bade3a42d9fe1e9e4ace5db79138bdca3925b025b9cc9f7035bac418a63fe7c4d3b6c849c58487a6421296bee728f4c3a63269d6b383927ea64c2957aa3dc3dd04ea87343dc99fde98c1ca35aa5f23d71610c2e969d43dadc7c154f25c048e0fe7624da0991aeb24c8c12984cc3464b1442661e3a6c68f28952eadf2affb2f4a7cfe46b3849fd6df638ece4cc4e68975230dbf502a594ae6084644a1d27837306e922b366beaa0bee2fc1ecfeab95b5649e36f426cf9fadc9ad56d6c272495564f403c9ee0366a1fbc7517ec6e2797648e91f4769932bc72949e2a4a52d0bb33774df64d1989935bf9a35334a6fbf8e25c5a729368669d7fc0ad2d04d4fcc8933a7b5ab9513ab95b523ea85dcd8fc7072d659c71c9b2a55e058d64ae92d530a03ce09b35f52eb4ddaaab12a29a55f7f0254d5a58eb8f4a7f4e7b76b523adf868c1a9a4ca1071e51fa2ef8994ef89452a96266c8ac28adab191af4149a4eb484b240c3943650ffd260d5a814069f4c212aa58251a550f8640ae9a594d2ee1ac624df5a6b393bd61aab50e7a444e9a4656ccc088d7c89b1f4dec0d1dddd3dad4f39cff3bc52fc22ea8b551447cb7da52e34d55abfb13476638f5dfbc81dcb15b6ddb8c4d671fe60eabaa761e765a0f3eadf3147c8794474dd5c0e9aae7b5a9f869cd7dd75f7b4ebd3aebba75d9f76dd3dedfab4eb0898ba0f5d46027492d8e340d78de241713b841067ab7500fb84fbc6a14188d3d559ae4b2337f6c8912dcff3bcee697dcaedd8d10355391c9a4e287c32e127f589c7cd84483e733f2937ae48d2c70f337cfcf04ea149cc91006f9c3e7c8432ef6b40ce9bd57bc23da95df7b43ee5fed674f5ba4d12d7ddc78e765e398ee3388ee3388ee3388ee3388ee3388ee36a9db4ba534abb39dc755d777777d3ca35689decbe1cd85db7933cc7224e763f72ffecf684288195ab9e576de9bb5cad956b57095a5b2a95beefde0b82601886a652a9f481df77bbe794d9ee9e7324674dc3e0dcde93de1a3794ad40d01302dfd0e61b1a880dc6dddd738e4a9c0441d0643a9d4eddddfd77ceeeeed004f63d813da7cf39eb147d4e3cc274132e0a954aa95430607777d7c072278d834e2b67f3b5a5ef9642d3c9844ac1cc900167e3349511c12a04198f474a280bf46c710f1322b1b9b401558864060479fea501b2c09c2233482311f3d3e2934905f3e54d8e196156372a6c9322c99325229f0543eeaeaa904892b4d6b2404f085c92d96b80b43cff242a117568eb086d9d921863421850b9868d2350f988171b378c781c8e247d4e38e141a6de93df58b2d61b6fe02862668ae37bc4fce08732fa22a893c45f37cc9b6cc949d296931436416bff8ea5ed466eec71d67272f75dfa23341ccb1538dab1fc25b9fbee3c0ac449bcb3c5a9286012e96c713f2344626f0662422436444247703583ccd08000113ee6005933480d1b0da030d38c9f3f3383d09841ba213a9155c30199d2603da024cecc1a1a10c8a2cc74cd44e0247ab3c59db2cb4820533a49ec71a0a33d780e1a9ed6012c0da8430871b65a07b03a763cd9c103fcc6e9205a14a78b2b593b7a230f90dcc9e2025e878e50d67d0bfc17f7f36d80dc08c90122cf9f76a64492c250235ead4ee3a42da2d2276290d58198863de24e947d66c583478f1f5632337a803ec06f9cea439e6f3a61f1249ac42e47022aedc1730ff07b84b2fa3fc04961a6eee76398550d7f7d3f5bb6e421979e7b1d27bd34561964aa0c338e10817bba680f312112cff5ef0f50c75ff36f0cf598534c4c8cd7c5c484336054313131a99818148ea926ae19609ab3861bdb3a01ddc50414c01b9e8ff3c4f28130608c804b8be503473e03ebd6ad5bb76edd7af58a4acab6d5ca534823d15dbbb96e9899d45d7bdd335aa86d97bab7f451237592f3e4a6b543010a121acaed556ab99dc8b378911bf9cd8fbc8b279dbea4b31d985cd21eda8f6d293d5445cc25b54289a88d1ad12df48822d12eb99f7a99f1d07e3214a01a2485ca50ad12d5a26a94bbdeea96caa522d5a419d7c3d172733f50a270419c1037c459e188727336ae88d3c2ddb8230e894bcacd79e966b93b9e1e1db9ec7c7e8082846e7cd95119c2f16557b392bbcbd2d93aa3eed61de5eeb87497d479c9fdde0e0fed0768fcd28b1294db13f2a87855bc9a47e4d93c239a2f3d2dde4dc797de91c7c5ebe225d999e5b1b41d5f5a1ffbc3e34b0b64a358294243354b447e69b3585beb4b6b64b5d82df6c822d9a4598f2f4b3b3c3ebe2cd14a3e2528404142433fbe2c55a9d57c59222a15958c4ab7d2510929852f4b5d4a49ae2fbfd94eeeaf87f6031464f3e5274548852fbfa12ab93f2b1fd167fb8cbedb0a5f7e5bbe2316befc90be2e9f97190fed07c897170a500b5fde2029b9ff52b943b77689aeedf5e52dba46b9bfbcb7dc7db95ca49b34e3b9f912eca1b9f025f8034201a304090dd5827c095a018970be046d6011a805bc814720129824e44bd04b387be1cb9027ec097d7e808284867c19521922f26558b34293cb304b680b8dc25b7894f365c82544ca5f8649a19719b934edf0d07e80625f9aa20401e04b9310952aa69a89c864331915f9d2a4c574937d693a3271c96dea624a9af19c68307c79f239fd18f9f204748a3293cb939493d069e8543b111df9f294e56443f2e5c9e8a4252697a72da7a313d2296916802ff10e4f0c5f621af6c9914b0c0503e1202c848764f81257c1b519bec444384b6e5c94fb67898d72ff2c71898f723f4612c097b80b4e1ac097a8d98e4c2e513db41fa0209d2f51528408f0256aa84a6e9495dc28a2dc285b6e94516e1abe446dc95da28e7201be4421e54675790294dc5fbb6deea721296cd66c80dcd872bf0eb3dcdf412fd0f2e3c54bee77db0a78aaa841e109b99f26b540088adc5f8fa2f0051008723f671405ad88dcdf11d5665a0ccd9272bf876483201fc0c8fdf6a6c4cf13b9bf64fbc10b0f104fd482c8fd5f9213423b5d72ff3d4201501057e47ed08808da1329727f48b4c44c8831869ac8fd26249ea3202672ffe936c4163f48e47e6c23c2c6f324f7a36a52e4fe0f982017a02617e069086505a0e10910ca682062089900349900af13ca08700314d3f90184321d22a8e4010ce00510ca0620809f219409c08740ca33ccf03284b219ac0c1f43289361081ee41862f80084b21802f048425900b00cb1939120f923a10cc9913712ca8e589e1e6423461e8650660486978532189c47882c937d9150262bf2000865458480220300001f0b6500887d0e6531fbe428679c9c3f2794e59c2712ca729e40c94452c8447e48282332e45f086543c0c82fbcf04242d90b421e279409b141c6a991713e4828c3592116e45d086541b004f14376c185bf09652edc3c2c94ddd82b320cf6af5006b3af6f2194bd7680720b626ee18184b216803c0ba10c0810320b3699855f2194b1b0c2ab10ca5600a20459051e5985b709652ad8bc2b94d9fc1084ecd2915d9f422873e514be2694a500835c53f33f42590d0f9efce37d84b21f587e98651f3ebe4728f3d1e35ba1ac0715b9d57a3294b556c8e4f30865a463f1a188cc83c7ef08653c76bc8e50b6c31a21ebd0f134a14cc7cf32cde708653452e41c38e7f83194e5e82f5ff2f862281bb1cc649045f13f94895ffec711cadebf0c21e3f09171fc8d5086c3f38db711ca6ef81717641b37b28daf11ca6cbc9a0014abf1ac5056838ccc623d8d50c6da9269fc4c28a3e140c83333bf0a6533ab9709652b1a64995496f919a14c66c6c784b21951809263befacb94631e2694c558985785329822ccc8aa4f8532954d3d2a94a56e9051a8c7a10c85ff14ca30148ac8a7d39b42d989c817d387a1cc749443981c3e18ca42581dc294590cfc1bca40ebc54abef7bf507689e0c5279768e4d2db5056f28eb27d2f94d9a3bccade77a1ccb3ee023bcb75cc250c2b8f99beed22d3327490e9137c7097352a2917a0913871bb179bf4d0bde753e6df8e74d263536471df0df7973febdbdc3c397ddaf9e58b6368c4943b71c67e4583a459954f7fb06e6dbb5b6bad5b4be783a7dc8dd6e7a784c0a54d05410290d6e53a6104a00f5cdc27803ef74604f8c310292040a06b4b814f523b5960a1c58a7b85103f1f00812082175a1cdd28cc40151801e97ae1aa78c1113352b3d40f503842101c370511240849508bd18fa942071ff08e62f461c6b846655c208801ee24715604a324a8f4ca972ca3a2660ccd8800000005e315003028141009c562b12cd003a1f20314000e7f96525640184a03a224c87114c430c618630c01ce104290223444c4193901f084e2bb66d93dfc70e4600e3b16f8fc5e626ef98ed89cd675f897f5a16fc50f3cf7acfc7f3aa34568f8ea4a751d949a5445523269831f9861d798a5855a0063a27c595c2968d89ba3dac1ad27ec940180bd7559ae34ce560cb529d5873058e51a56c2c715610d2d15b9d4aa6acb7d6cfbab5c4a0f95c2917572296bf3b3fed69058df917145181a75b2155355504f1905ee038dc2719551e926e4420b64754983dcfa07f60b4376cad0b0310384496e592dba2bee292423a5d10d15e387e7ef49cee0fc05a7b78d83e5708ab5082bcaf2958b7e84ef038129bbd7030d0704041267000e1cea11386859023c193c04f53640d6f9b8e20dd78248623f3ee64dae547cb37279552182156c8f86beda52e57a8079ffb65fdb93df05c2b795f3c032878042a1809f37d75fa50e69560d6a96fe5cf6b2e7673fe39b71bfacc32d2e3de198c6c5c63035ab6f35ff0cd3e6228ca85568b76a376a3e248d1a5a0b2cdd191ef64cecb8ff3fa2fe306ce7f158fcb3344fec1715e3ae1b9febaf945c91b6a0f3431cf60dbe5d05047444584b02f0fb899136fedf522cef077cd27cc058b53060d844880cffcf9eef10c0fcda2a3c31ac73071b166141e0053f26ad4cbaccd20050c3f02854ed7205dc213bcf814e98f47f0eabe7d840c2dbdb118188b7c42fd1eed83c23a63df88f2689a973b90a9088451ec41351e34534aa356c4cb78b431e67d8b11329145e696e2f883323c6ad3386119fb4a0473f13f871cd630762484b2bf93511ca298a7d1c0af837be5d70253020a7bd31637b471550e10e6d5af2805c1e5af8f1ad38ad7aeaa3c9ba218e2d39556dca8b549e3a2ff781d21d1b470e0820feb0e365fe9e35b61f066fc9d4560f54027984c51c4461047e41b4aec29775471f8af5bb1ab2900e137048bfab4143b91578b3e8a71ccdfde42533caca59040a0853a6744ad604dc80891cf96eb40e2bf5615c2b77c63abf6199a01960cd8a88c68aed109bf5f872c30a12bbf08dd04b0725226a8b33f3b6b1867c556ebd20d2bf8258658872063c6b468a0420be6c9604d807d44d22d377640381debf1a0a34cf59b7d9eafcd703f2b83265c9ce466089337c1ceaab96acef9f088a4c79c7decd529b547a886009298da7dc2657da0db00da0a98960eba4ae554a024f55131f93b96c6b93958ccff9f0d54df2f66aa385c9df6087459750fea1b42d11869825002c5686555442f244254131c53743a7d26b236e18447b86ed5e910405149531e11352fca6229596c762b936859a02b45c559baed8e5a4ebc25a45428c0fdbbb8b760032ccbc88276e8584e468707391021ff6b6054864832710b6709591302734e7000d2e3c4d7b002c3caccde9216daf5eaeba0f2e5a51be46273582a9f42f666ddfe245678a8321764ce2a0df9d995600a37429876436333688b166d160f9c890a9c2cad9b0cc7ecbb2d9d9c2f980dc62ab97480a820ca833ab06e239eb4ac11cb8d3f31d2680cb1b5320321b4e8c7bfca60de8304cb54b2680d90cea72a5e6108e758d8f9e021abd46bbebee66900c64211319640f5987519946ec60a7c3183a0314618f87bb4bf1ee8d9b5a862186f13bd15503a7b695ae73b7320bdc931d04fd91ce1e0bff3dc49934e23fcecb71b4ba14e1f07d2c1cc430fbed6549f818c064faaaa85d0b5d2192d56961ec18c8e5071250b1197a3479d561f5e0f76e29cfe08ac96c435e864fc9d13f1bd1746d7175d5e7f43fcc4d10d07384036092250804618a6850a29ef374bd4e91d7c06c1976daf94d5bdd126e605f9e236f055b9c3a22340d1849258e69dbb01fc26c4462046540000c680c562085614c66c2a85778dca6e1380fb94771b051e136a60c1b8498e7487b518339b44abdd829e5f04ad293b20780c7de175b1d0e1b9809ecf7ff58182e6fd926c01c4364e89512cd62dfe82f4f5b2501a6c2b9503751e1ce54c3fa1c0b30cad361b187235fb4c12bef0385f4286ff44df3ccde983937e4b6e86116d0eab737361e4c1c4b61f11208a207be738c3e708041f2203e129a6f9583a7f3ee10a0f20073c0eba84494598cd3ab291083b0606de26134200f31120318c76397fcedd2e315dc2f641955117a00d9af5ae341e9c43819583adbff62f09ce6c9fdf00dd413fd096347f522e55f121c70b66412dd02b12980d63f75a959580244737b5e39a23d0bdef905006846c7bf331e162439c12a50eacbdf820eabed448031b9b6a05617a9532b8f1b947829c497bfbc62364f108a2c3e0e6172be22679d5f9bf796709f58946a8d5fab11bc639109b90b4be6250c4af7516783deb5e15b188e3044aefd72de08fcb0cbf5085c4fbcbadd17d3adca4d72fb71a62cc65525d78824f71e9c733beeb0c1ad6111132ee01436cc202260307df277cf88611699b898f6ff129e7ab9be4d35454895f31dda0d59ea974d900a452c5ac1183041151535031bac60e64bd37eedac20579e72baebaa28b8d43fced4f9887964665f2bf10cdda8e2c242713eb875eaf5a5096efcbf8c7228a372702bf28678418b17c274f53d9bf7640f099cee34f265752b9eebda917e09b2eb786e7cc70e82c1cbb21cf8cb57802c0efaf3a208e9a3a6bbfc30bdcc2e4ea92cb1224d777139cd3a49fd91527c818d98fbc7adb30541974aa08d788f4498ee5c89ced0bea471d8a10495c772a2518b2cdd39c95d3b6c4a8538d111de7b0295ee6a70ee90cb7c0b4ea774678a80107c8135078b17a9385f4212b2f1d1d2c2fba4afce67c122c3a43b8f8c03ff3b9cb6180660e89c4a49c1b5e0400a83ed80cd06056eaa3738c36a22fa6896b260d9e67c5819ab13600612d05b8294f62f1740ea38b67944ac9508032f2b6d0ecbd11df9eb73b11d77153ebae3ae51ab06605ae03c4ec16b9942b7b06c740433da42cbf880dbfae1a81950e1c5f7096e44b71eb0ffae16ec38983ac9f3160ea143776c8fd53846067e7ce9ed48fe37c793e7ae1b4b04b93b34c0b4f1add6250aba9b05c3ccdbfa0a4b7fe69737a6092ed05da5adb20f006da185cdc07c35ecb1cd91ad4d6e1cd1e820e9e7ee999d8fb61f7dff38a4bb40798f49cc3e77a79c459614f15d071a9ba300f36391479729fac0c189dfa1ab432bedb9eb7cf571f1af9ebbaa390648c79283270cc752a2a1e76e26bfdc84709ebbb0fd6617ae500b9d95face5d9e3d9c1d66428dc1960e9ead4fa19bc677eeb473b40a02e22920710f55eccedd054ea8d470c1f68cbb00ca5eee3e0d9eab73c7f13e89f491b207503b05b974eebcfe7621c3b225a83a966889cafd2ea633457359f36f00559e91b5362574cd7325b213ca37111d68d7a9c5b3e1276ced48c3f680e55ad1235e617eba3c518f8aa742fbe9641027c96a4873a82dbd8faf0f2be267e97f182545c508b68509a61016db72d24550cf5b6941b91768af56a64e9a17ef220043b9ba0cf7abbe83616c89ed518de3d097b758e3ec0630f8474e18672ed40dc4a79da24d28f2af301a404b441a4c3080b0d2e13fb457d0d3e1be361f88fc574e4639fd32bc9182eec4641e3eb0d2c94d92ba5c14e7ebe1cd75908f6c80edaee6ff40fbbfb0d36994ac2ca7001461c9a14956249505003f5b8e2d8f20f54dc381340e1cdd608df4cee0cb9694e7b8b994cc0a88d06358fbf6637f2854462a469a6746a7bd810b3bfe5f47b19f9998a9e12827254e387126164cece7d51f1f114ec98c597030042088d6c35043c002ef1743548e159a0f662d0c0cb73e045eb1af1c2f7657c6858f9249856146012184ff8e511d87a03030ff3f904d3ddd5ae11efdee0ad4cb43d962f1d1278b8dca355f0ff2352d9d6c27934dfc4b259a5dc907ce8bfb1f92789c49c74fe1aa5de4f561e53bc38e68c2cbfe0d85c5013845ce2ac2d9671002be01033c65e538b9c788ca7147d0fc5b4acb91f582d74f1c4c6d7f8e9670eda79462c718bd59932f6cf8e56df4c3ff2a76f80ded0da27c0ff875b5c449fecaca8c5a5f02ec33514319f899caaa210528cf71898c9f3b5d0e2f74af5378111fa9c20be5c4b0d11573857e76b7c2d2f7bc56ad04b70b1e2e017cf779ad2d975a2c806af8c569b6884e9554cb7f44c95fe941583df9039e1ba55803907f47c6d5709d956216b1af930082fb59c4d8079f561b4dc14038c5566e3a3f1627f7676d90eb7ac753e26b1ab36780bb84d25bda862a1de7d0ba3d638a507a60790a0b327422118b023286f83c5de169808c93824ab64606859305b4f0e13b8386dc6128569f427c35065e9e46218521152b417e734bdd0aee15b2842a0ccdd64b7573ab0649166cb89f1789fb2deae36a65aa2dbfa4d5f05648548b65b081e7eeac862ca0fc84483e23e44f3ba31e472e7d1201c97637c8a9ae9467b17f1df7eaa4691e00409a2ae559558f9c319a6f72bd6bb0f6b2926ef84b39ce583ae61271061abb9286d541235bab6ca698a5a54859c0fbd362f32277622a9b0df124d99ce6c191b943929862ee760acb6e95091287cf359039c662d6e3174238801e60430299d412c3c9491bd56f2a63c626fb727fa7e511a62000e9d1de6dd9654afb6c298bc08913e1f67861aa1ad726b312a77a4e166320f323edfde2f6518cc9902a2a7a2d537a71f37f6ac7fba40368d413ceb36a1e8e2b6cbe3bba4acd03487aab85089e9c2d3cb9486f2a5a946b39a8b7f95c892261015edc62128ac42f69937a33193aac1096952d8c9d4460eb744602bdd0331c7ea6e19e1fb22af73c1deb417a254920085de3c1a2c0e14f6f7c916576088fc4ea25d9e7463965a6f741c3931a0852adbbe87725ddda47f4f8144e23ad89cbb03a825639b7f98d6e149e8281346a29cf01aa7253422f191b09d9683e9e78149313351a4464a374c92ee535a05d8d7c48d6b1d170998d86d0d8682a8a64a313fa57b376fdbab5d9d83871b59915d972308d3ea05b410f0052afe5473f3ea55da0ea46e2d2833ee8a0fd12bd38dd0bb04de02ddecf36fa3636822833725f71599aef0ef079e0e316089018fa57e82d31487209aa21ddce2f9f46ed9ad7da8b82d48a2c67d85200c1394e66532f87d272ca768ef65279c0712c9201493045f8d88c73620bf8530d3a0a734df1a58abe6c91eaefff8e86da4394ee900558360f44138f839e5edfcdeaebb51227cde7d76d9c20f1711a7099a0baec1fbe52c8260d5fc72f5d8d56f8a00f5d6534f8f3adaaf23050c428f3330ee584e87c74efe4545ec535eca63adef3cdd96edc34c950f99ab86932ab3a1a43db7d18afdbc9b356b99550589a30cb7a65b03465ea97b7fc1c2a6fe1a609026c3f429a188df812d2044097d28457ae13e68a4265568e260630543bc16c2254ac75dac763624049f7747e1552ab18382a91370d1e131c348d8c88062e5d15596b324f529d2532158db40cc5f429b87c540140214ce86e9f99a5eb3d6acf6b3f107f88703a7dfe8cc6bc6aa76781842240d6318a92a5a2d35388e60365316e7d708dc67a41d3dca231afe301d8a27507809f3f22c3fa9a32b11f9ee3aae4d09ba86990e6074ccd8aa7b48dc6798214c5b2f9280c3006414122a477f417efe2bb6d9cd3370e607ba2ee08e1008fc59baae55cc66556f4065bbd50d6aa14c52d69157ab3abe7af1c719352edbca99cc0cb26650f5d85cb5723315353a526a254d2314bd23f6ab323dc925ec46dc42aaf09ecb3d4fedeaeffe708f454ed653c12d38d5de73483b7931c8e7f7f349591ee17e59982e67985432d85fb170cfd35baf5f34bc5f9d1f1ef985ed49936006253715ea5dc9507d2b86466956b4b6a4fc4b4757a0e0bde3cd0ce1a914e77eb5beea632e02adc31c2b414cd765107a5169651cddaff65b604b85d4f9a9fd342c530f8a42b4aa50976ce562056dd9f8ff2bd2f81c48d192497440072d02634fad8774e432f70b21f60ac8d4e921f0d7e5440db60be9cdea96e4082812e7f83ba018a84502a7b4e54db0f6c7d458dcf7049ff857bf0f740d28b62d75a8df9d6634892874bd6bf65c582e93cb885be8c3f2752e8f70d8deeadadd68bde3b8355156e03c9059069015929a399a39636d0e898f23d013ad0a118eb0bf76ad2c0c0b0fdf639dfebf3af3a95103a59797bef451dec1906a9ec1d9b9c57782febce584b02bc1609610319ec548f17e67e23a418ec73703b2455809e68381f601530f37ab3e990c9e2541d49da9aec95cc6d3cfd225706f4239b9d0851d5d754eb7494edbe30e1e300f060f1d56945db5a7dc239e140454980cc01e8c1822bcc250cd50be013162dca797609498cc79321eb82c80633f16c1f9a78c923c5c86497c765754b775a6b16dd657c98e1afd0af6952c54823cd89aca46524733f9d8435802ceba8ba493a3fbdfa18e24cc46ce81bc1f8282c64be66def88fa0b1f986f6aaa050545b1a90ffc396c7135205f6ea593662a26706f4e03a8fc1b1a349341c4235368a813ac26c2c2818aba282cbda7a383407ab706da36a612ff136f903d47b8fe27d2799d633a5320c20bd01c49c4ad68699ebbec978eb347c3d116ac5706eb2224b719675e2aa89863748040fcee84c324048c0e07a95f1e8a805f2ddcf5e174db989820873f7de097c2128b34000e31040e65b10cf8fff6f3752e085e81b7c7311b4657c7ab752ce8c9ea931597bb569cb3c9a8265702d2d4583571775a44d03e0300938d6102ab0430c35e9a7b9f8b07df303605b1994fd305ffbb1fcdc8f00df0fb62bf32246530ba6ac669a79c19879cf16cb35405c9c32c8e871d5d0e154f23df9c3e065f3b2f08fb33ace4b16ce79092ba82ac3d449c2712a60f153006b57a23d308fd0c56c9a68d16ce882702e4c2cbfe5d1b0594ad942e4d556b9f87b6b891670ffa04b04bf205fec878fe81073696a0734dd619aef604d3b861a51bb8a226bc699a98cda6168cea134b8e6ebaebde81a5ec3758a4ca36b5febda8bac598798c6c4eb5851e7f1d0f24ff92047d2ac934e36e052dca87995dfc2bff9bc607332ead7c1b554f53a0a68a1bef269e45812b841a6d863b88a59377780a424b67c4b694d3b40031c748d578add470edd92e9efb82349a011f76346a68b2438280af11125b00713445806e84266b65ecd2da6b6bb113db161fa64a6422ecc3c07c1b6469e5aa2965eaf918c1236a9224cd018f76b43e6bce85ab536b61290c29327c7de6b35a84f9b73b0ebf11a50b8651067d710af6445bcc3dd2302bda5df2a1868f02b6df3d51bbed67979e66a19e14d263470adeb5cfb0a45ac31a7a6348acf01f23497702ed07114f72052a12e5553a949b534c5f1551373f8ab043502448a616b90dce3e0662b9dd6ac349ca66f76bedfee08606ef3905532a606dc68fb463d4cbba8707165bfddd211123a016dcbc69cc2429e5a8973b3dc5a713ebd6a7816ebbc5cef4ca0e6a16969338737050e7f979721fec93a7b43c845738c3bfd2f33e0926f7865f8350fd6299e2b8c9b72bc0394d4dcfef0016c05a282086d18bc65a44390361521a1c00c25d9d18d92402ec02812c6db9a7ed9f05add7260d5b0d1f9e99a2fcab0a1cf370b250b4a5158037babaec9480d52beaeb57b2af05cbbec67fec2f747529890670dad9cac9dbac7bd21886e1d7e47c9608db612b7f003836eb0f5596b1d7b3a6de81e5fcc69be2da5cedc8301195316094ed5838e1cc0251d089e92f2422782f979f413b71dce2e4e6517bfbd5e1876352655e997aa1ba14002d9e0119e779890278292be94985d892048cf7351cea7bf4138d95db9731590ec026b667330a1a618c2708df7060c9c2d8faec2801f31a1e6d33a2307bf1acccb9b4d31c3b18f1578172ada08a9f377d7bc770db70cd5dfba0f39a89574052c4b8113bc600a290f4a00837c1946b90a2e9870d942c0d56b3761ab20c1306d87954ecf228f4f0feb1d60e9a25a5af002f864da4e7e15281bcb2a4f2557ac9f90bfbf5a2348859c21b9a8c093d308c9ab9721d460e999db0c97c4cdaeb8add86760553d4af376b353decca037db81561bb1675dd155165a1b24729172ab8d5a3ccea313a49fb09d901249d629dafcfa779c136e47fd6b17de46485d0eb5548bae4b90b17c467fcd31b1f0ba9408848a2475ec11f98a7a01c9d314ba1e0ecf613de2ae3a269e6aa5849bb41b87d484fb482f38ac8dcd6808d6d3de7ff14c35a93c55eac9a96028cd225a23d451ada3fceba1a79c24c24f6995bc986cc8ce53ed527045066b79434419b3b91e4d81aae5bca65a7ef2c3a6320228027c4a07f6c60c15bed689a61b4922852b0f8393f7fb49cc2a39b38d10fab9f3bd0ba02087955bd27ee0dd23f3c8fe9aa79c99c620c80f560eb0742b9cf231556eaf7a749904b8919430ba394eac618ed1a8002363acd47025e7a5b517af8f4abbed12203b4dffdec78b9031b6d685019690e1379fb33c13eb9439d8892f9d57a2ce776f38463620f0f2b2cb36ecef9e5bf73312a4b857a53c2707d138048cc61d57ceb821577b23833796050c806cba23a365feb0cff3eef4138157a5955917c8671ad6d4ba3a48ae51638142dda1733b276b67739ef9ec3189cf108ec956bc24a6d331d4a61ae6b808c226010fdd69c0b59f37329209602f7c7513ff9617257a6c54829cbd68341cd0585cca4e1a2de544c2d41ffc04005202826ab476de734ddcdaad34b815215a00e1087f1d6e1c91e1ab606d65a203dac382a0c980f05738979b312dbb4ab70b82a3d715c0b5c78837a56d4e89cba9994a536c3645716c007b23b46df1ad7f275c26b5a9a20960c3b0c3a63c55fd21d96b72d675c6973183dcbc970a8f1677131b2e6ed6499e47dc799eb82a4525d4f38228c6f7bfa60c8a218e4937c318ea526946bdba77d4a60cdb64789bb60d09566fae337641dccc236fc92182a96ac1790d1d8d090b5486b4da2bc60bf6ffd3981c7e8febf572a49f8592c11893013a7a87b23d262a27a0b0d9bf0ed7ace0b7ac4e418388f7238b8034b67f4d35a95c528aee8d5154704f28820984c33f091c4373f952584d285a459cef440681b462b77d1031235aff5920546b69e5e452ab25219625ea7d9bbb3d35ec6ffc1b79a0d890e0b1105ea967c161d5b3d2f402cc2dc5e255d95bcb84cc7d11be4c97e25be9c050fe40e6bf1a3cebd8b2511d04830cadac8fc33b5025da098bdb3cca84988ce56dc8bbc50d97ccb85692c438bbb218ef97fc5c16a587abd86b24d51fc6b990add772f860ba1e2d30f3589f7e7fc06ff74f6d18dae1b89b8148189a0507a4c9e83977d621e5a3e041ff88b0b348199fe96d8411d91789a00e97ac820592f867ac182cc05bfe036f1e65d651879cb3307e3bd38c40e033e13e27b2de4c32ed41f2ee2510603f4f91d839ab2363b95f6273bf12774bc16126352b37bb363d7a48587dde793f551661b157968b63610dadec672308b35b193f07366aaecd4f17fca62ea1b6d0f98a6e2c2119ed8a892f37551263e49291157fe4c71d14dc9780af1855d6c9880088fb0fcf158c90b570159bd93f454e815ac44f48cdcabe0c73f625e5674b1912cd59fa5190b4fa17a0f0eb37e47eb6a17e67f3558afedb59356ef850308f11ffe0f519a720af712d085b710a5dfabfe0eb261aee550495790677af8a0ab79f15572528b69eca33142b37ade2903824237c9fb2e41900eb7ec8c6ce59dba46aaf2d0d80600fedc303e23b7c51e1ec43cd04da0a7036c6fde023db0eed8fb6454270c655dd852cf76afcde96358fce213e516626558099c037e6d19d16c8b9110dc723213c021fe3311c093d866d4acf798c87319adfe4b108764ddb2f47230511e60ae111bb60f7e25a613e1f6fed4562ac5f504e7f85b4f666b5f1372b49c98158d7e4c40dc92e84844efbb2034896ab6fc13b5715a716666158708a8eb97911440e3955186e425bb91ca741b94a32807bacd463b37d3d49db9652b36eb42de31212967be14c32d5edd7945a25ac8814cd08a174589e633614e27a35638ad7091aecdb56fdd89a6c1abe8db3deb9364b8673adb4820f4ef846c40ce9f8ed893a76ce2f17bd966f45568a20c54673bdd3868aa4592dbf1fea4c84a4b439ef09c24b9be061d45c8e2b341d0e1a03209fa0b83de2f642c82628eb65063fa9cab65d4e557aed71dd8d52613f36eee219656e07c34aa170a888acc683d5957914892e0acd68cee75ce278c99a24e18f17efbe5a10664b9abee13be93b0830cf5b45fd0dce97c2a4514536670178d8e96d00a4e1dc24c72fd341e533a2ea2d512432bf29a8ade5b8472bb6cfe45d23fb522deaeb5bcd568f458a75b5ba7112e3e9b774c6ae5bb652e109682d972e80eb576e1aa48a370a3f1f421ea4ff3d96966daef56828aab05700871c645bfd171641eaa34fd9d5b28c1c3e6ef07d7db80600129d9d9acc252d903d7a8c7e1be35112a17ac2e9b4466ee56b930fedd2515ee360c67ef0f49b4eb355ebff1d373ce8f6458f1ec886c98039e7b2e2d10217378cd75c3ed90c76a6b9dcc9149ce431759ee5a211795fc89df1329e5fdcd9d2f789d37a940fafb2fdf536730e471ac1548cb63e8e20e6fa072e0f4316575e0a38c09c27860977d68a7ee2db1b997207611655eac16c1d156d7310fb45fbb65d82c540971ad7e9d274e65cf2982257f1f40be1531d35094e7631fcc6d0aa68c30d07d888d512ae38a4636af4afb66c8e670fa0f60e5dcaf0c4581de7dc8b849545f3008ffddfeaa5f8163cf36fcfda5110b4951614ad75007fafac28be4cdd7786412731c6e634b801ac4a8a1b1df1af8391115ef50196e8128c4f6a24010c41f21632eaba6faa730e43e72c6eb0ad0acca5a9804e81ed4a8f388639d116c3cf89ea661231d824fe05ad48a36bdb2ff9dbcaf1186c97012836d2fe48135a008e957b3ac85db2e61fc64a2fc3e13096599a8e0d69b6cc192b8aab766a489a939c9b8059d3e7b0c83298125dfef59637d8802eeb10979cdcc52986dcefbd2b5cb5da2c1fc5bbe3753107d47f9272391a893b0dd64fa8bcc428e3525ce1306c98b5755b1ea6a448c4c99400777a69cec4522db2849a7c70a235acc94b6606ba68134e4068abf711d3f8c39113634a3bbeee7c745a1003041341df0f35453399fdd22a468ebd28ab2b6b923e396e8acd2a73627e698f89a7ae18350b2b825e3b464acd65fedbc76db42836eeac9ba02f0743b1994cbfedb841f38b9f92ba88f47cf8299b9bcc13ffa5654c1c5f31d3396b047d393414cd2ae3e27feb8ebda7ae285d092b82be1d458ae632fdfb636cb428b665d525e8d563a0dcac329ee2372d962201c048b38c67e3cb24e5ecb8993af74d421f298f8d3184fecba628fb97a5a2de8eae0bf2f178c77d136548190e4c5d646d8dfccc026394614ab843b3d6695650e7dc4853bf15ef90b357bce373539fc40ef474f4ac0c7d9e2578ef9c1b1164d9937bc7718108986719bc3b5d2f8381ca7abc708407827a382bf8e11d911a5933c1c9fa39f5c06e1c5b00b441a51b9d22660107b28d34e0a05d85570c5e12ac08056fbcc75c2c63838aba8d863769750bbe83ed4e025dcefd4d03fa2318e199a98b6d350bce2108c78ac1f2e07711babdb096fb4e1269cd82d81926f0701a42dcd920dbbb173c56770fdc321c30d31a40b48fbaef2da0e623e1cf5aea1157f494767043b4acafed24df05f3a62436e00888fd41d1d10d001c0ac67a35b259451a852f5e6d0ce9cd7f242a7c8e7c4f553aaf6ec413cf512c364a4684c6def14c275c58e64be8fc69a9003fd66d0776eb85f21fd16d5b1dc408e47fdf3830d2cfda2cb20080e05152cd58c12b7438e40466950bd6ce5e7cc8dc62ba0adbd0bd39d86cdaf46f06be013f580d333942ae19ba8d06455b965321ddf886d87af3b08a29200cfe52861ca2e4ccf7802317bf3037e241cc584fd1cf19bb3b23ab672fd9f65f5da04f4e9897b5b6f765475e6a89ef3d2d938120ca658578d324dd4cb611ee3802392193d23bb3f0334402431603381fe19299018abb185841df899683aa7fae55ccf3e03a3b39877bf073b08dc2155e8f8d9247627aca16beec9fbf8fbbc58ab4642f634cedc5ac1622f0d92584cd420ff35b88bb2ddef7db9bb48b6e62e9aca6430ed46135727b083f366a2843a71809aeaadd40bf28ced0bc4457d95ac278c5516b4b73c7db8ff486f8529678ee527a899ee9d203b52cca8484c7a316470547ca60295277b16593d95a5c51038dd7eeb2c270d62d1b6d13a047fcd2a47390f8347911b384455c743c9e332a94998fb2ccdfd12c08bce710ff0c9135be39a7143932fd7be6bdb340798a00c6e1137f1b7ecedb1268e1a7d8408d0784e1e626e2169ea0263b3292b443c518a1403529fe5b125eebd1b3a346fb2a914d0c202ef752a67b23d4a774ff26793692c1a3e1b55dd2d525b672536a050632854ede02aeb1a872c5fe4cd90889c1d37d130d73346f7b32fb43cfd821d6bb7d5f33c38c71599f5ee9a051ee8a7504c42d4829350edf49164a65dcd86b368f314ca4e5d355b0fc1f533c469a0de8bb90ef3b103f77e3e299adbc15c434e2de9ed9b2cd38169f6ab5c68c1af659295b2f547c2073f4edfade67b767523f0478b447f0ee7d3c78ef1c109d100347245113f334b989ff000cb259b44fd1c4333cd0e5af425ee2276f02dc09ef713e17f13d43f1e7885c999c9cdb79fed73e491043e5293bad8c4ec2999af41edee9db461c1d3501f2758c51dc6975292394aacf52c70f9b574b23cd1485cb717051f09af5c14c596e9125bfdc3591b1ca2ad1c5067ed6a0234376a13a17286f015901dd987fe2a1586f8fd03cd81f166b7003f808631bd8f0f89f7404d0ef48e696461d39a17504d0919e7a20cf889c306917f5be6d3e4322bef22340bd94481deb008e9eb6a5fe8f07e9f08962e5f70031ea76a9a25e4980fa50346e3dfe420f2aa0633b868213fd41b4dfd37816e975e175963bf66caacfaa0d025dce3a8e0a17e1518ca76b6c5dd08e8c1345ffd49ea9b0cf344dcf53b563b8b6ce27c0eb8e291a8473d448b8e6ea75c11f69e249b2039ab4d699439258d1cc0c0573502512f6cc43254c6dd130df5d3430cb30b51688baff7653c76e93cec4e13b87b827f70b8380c3fb29731c167848cb9fa8137976baa9cd326a3911165e7097b69ecf6eb6dc39c733d16462c3dbd009b4424c2ffd2e3ce0b422fbef5ad309faf222f88d3f9574cd75532ac40655f8f0f35b1539615f76a1cb99569d0671026132766ffba3ae1d1a1723ef8b0cca6589ca64789b128060197a8e0a382c86f666e928538fe266789b82d266a4fbaca3703409190c772809080603f6b739dacea5b818f56ecb51d6642a9321ef16498a6223c8c4784f9d4150f626960c6f290d0886a17a276a515d76c424e3fe68daac02c2c2e80ee5498be186e2a5c1708fd20e14e37b94eeb592780d03df6f15a555d043402259bb96ea12658823fc959f9f4195d7200da3eea287534d3fa5e1db9d8c65dcaa22058ea7eaae7ecd7574b2d256d089af97f5770d84d3f88d3a41b3c77e916c25c84e79fd91380ce6eaf39b8b0c363eaa11619d4b962628c4a7bd132448104b7ca55fd3ef994b3c404eb56e9c820199800dec508401f2093b860ef9b2283f8e9ceff4d25675b16ea635919cd024c7362ca0eb160c48e1b833b5204562364376c93f536d2a512c74eb5bb7011c1cad78bcb657af839bd6102bb7b8b822af0d77b1f3170f723e7922b366cb950465a744eedc19397dfcc88cb30430ad9539603da14e597a339db74d80602eaca16a6838fb03a6f4bcb1a2980c6549560d4fd51b5f347ec8d6d7dec4cc4f39783c897483f8deeb4f26cc95ab3a7b18abde05e8d2638b2f2b5e845973d592beeb200ab3533157bb37e9e2cd653accdeef3d1660c225c0b84902ac81056478de9f8661f35a730f61d31fe29eff67b38e8eae432b80ea0c80f0b8edf87621b0c96b05d199ad49158f8ec0d23d580ca75e7ce8f2ae6e3192377071efc7229b2b12642617892c119d6f2163f8d629d0f76849f27192b953a2c7cc2c7526092bcfc92c65d945dbcc9d209a7127ac1ebd05e62712539ea8620ce4859463766649b4a512ed69e6979373ac46a2a51704c6562f3f92731beffd49fa38cd2915ef179453d8d86de99234770aa43777f20a72813500d816f81a70cb6314bc3e8bb08eeea08ed9dcc7354eb8a704f99d603fb48072920ac7421d65664caf92f597ee6c9b0c6641f84242e1c8b41f4701503a493d91147064882837f4058409320193c98052d274d626e7e4919ab595fedd163a7517a10b35440329957a087a116337a51a312b23cef3c8c087f47222c76eec4c605eb9f549461a15b4a9eca32899df01446614e85d0917023d464db47f6252bb1517bb166c8cc20b90130cc71d67008a8c839eada09c6fa9aa4570e2837309e35228f4c3aeb08f42b01939065cda6f3bcc8ffd3cf70e7bedfdbb068a521a5b615a7d80215ee91cbc13a5f3dd18007e5f6159bff62d19c95d8179f1062da52e5ff68e6206d1a0447fa746e60842a3976ca347661ad309710fccc874c51dbdd9b0e2090da2f2f6640a84d02d536ea856d5fc4b4aa8eba6bf4599ace51178906f1bd0c82a6fd8ad4699b7acc5d30d2ae7a4c0e78bcd9f3579f0b4718af87608b44b9f989e45b7fc491cf135870790244ddb6a1696b8a7273a04c6f694f0ed51cb91f81139301e50c2f1b16d197c171e14744336d49bb1295e974654e3b02d5a105f4b04eb64acb6845783c53b83ad0ed1b9a00cb0dcec1e341593f60b787bfe3ce4308b87d06606da7b42a4666d67ffef9b165767044b633415d510470968c4073cc0399246c31606dec19f9205f0f6e85ec7235498ca73e5972ab9944b668c78556039be47189c2e9867b3ec8c34aea9f12b6feb7638d32206ab66b1ec7bc64e6e28e605ee2708bbf4284dc49ad0b0a59839c69c14e15e1d58c1770ef83d2fda1b9d0fa7edc1d22a4ac3c896156ded9373d4a51978b90c88381de3c17ec97d303848ab3d6f884086780222a27205e03ed6f6cdd73093cef28967a95a460ce3b76753918acb27fb5aa841bf74adb84825bdc4168744712f0ca489abe364c3930d000c08014e27f65f5055452a119eb01dbc45b588825c4463b3c8587d440cccbc0ceaeb1f042f75061eef18d92a14cf4e0d378624b27fabaa5e18b4edfc0347eb2fec1b10ba4f45da22a1e08bfd4e751f85f475c1efd6f274614f85d92f4bc6656f2ca0ef7aac1ca30842321a2d09aae5fdf0a6c0d8c840edb9761a392dee6173905100adb780c712b99d4940c7f0cfbb42526ef15d5b3f6e2a00c3a9ad45774ca69f6bd961c9aed116c1b91851fcfc6d9c88bca7c7ea58605cf6331caf868ca4a965e31e48584e2ac0695068d80f1918d8253bfe8a66584a33ba68730c6479bcb07164a1b45405bf45b1b1c9aa003ddd363961d627ec2721a83fb6ccaaa8304e3bfc3dfb21c117695dc91e3ef02abb056568f83cdee6485b9a3a4cfa8bd11deac196e4f1b0ff96e393307104f2af2ab06a8a197de751b848e312cfa896ab6fb7cc59554f5ea4b516ede71686379471d9661df5a28158257cb21ed19389189803b1f9de472e8ec26353a81c2b90ef789103687fa1c1b18bf85d8f540bdadcc83c518c42b1a314318a378c138a39cf8fb066dd504cd8d8ac8849e199858b9d85353ea64810bcc929cb337ecb59497b6095a50da5ed0c2b5189ca0fa681a2411eb2d32ce0e20146dffa07d046b26f7721db2e88a587b7504b0201978352bcc271537563b9a50c9ad267d7a112c2233636a7e5b15c0edc0ae3f3a101234490834c87ac7da5fe480acea5464646f77c03e8054151af5f6a3d8b8c7e53fc53e8a6810a5c17be21d734cfc462731130221375a8b71a586959184c5fa5eb026480058b96b7a500a59d321cb2d52371a3f64fb975955606e5e3851979d2730ac1652d6008663cf7faf05bf61ba0496799b495a4948de996b33eef8dfe985db14e1604a9cc2d9e25a4593f71dd1d315c5d8b8222ca1397ef1983296062af2b0aae94d4be6d5275b11e0888eaf8c63e46b03e7f51a44d3de4d214107ecf20f6948249a698c07f7d4a42d3e009c256a283ab94a8ea1c30312cb9f72609ae92188903586f07615c839e225b4e63397c772414815f666d89a0222b6cc4428886cf5ee6f66e553081becc8de082a291a5990a00df1d9439f43526fe398e9898ca75a3e216f46042010dbb2bbe4028959d35f491086ca342742375833af84f8d248ad7c9491dd01b62651fd5a996718734504e6ea20620faf72441447c15eb4d3fdcd888bc6b624fd4039a96a32578927219c54dc6853f6419b4ace6ef3de1d679c74b9da9cbf34885173bd5dcd62b4a749645792bc0137661e1bae838b8ca4d540de005792b6a76f54c46422f6e22bb9cd4780737b3ba3286a951bb9a47c5344942766f9d4fdc42c4f933c9ca3bb98690af2bad749070e13e306140cc18f54e97700ae054b42f9d6edc9142db9902abb843be2644dd23a485644024d0e4a9a37eadba0e3bff4e0ff81061a1a4922aaa066136d1283226957a237ce61670540a56b53307386c999cc2f7f346a599b6ef3afb9c6a4ec18bb3c581471eb5b41597cf324ebb75614eb2b72fae41fd4a0345e33de4146ecc3daf8bb5ce6eb4b40b1111d565bdd6ad259c25c1476cb5a3c4a1f81f1e39d46762d60703fc85279d1ea93c81bde41fe36d2ff4ad4fedbf49dfecf5a8e32a14094815738da035f3274711a134cbcc1f769ce01b106e9a0c6c405cc0f3cbb9c8e63820d919e3560d8ce7771de01855e0017a028f7f4709019f97faca85d71e955a47c07a054aaa37010fee271410722c72c8f233e1e093241ef5f051279154ba4d88004d87c03b02963d9c392c707d0f305331c573800f72a4e32ffd39e8f398b47177f07219ca19cc767fd5732fd5dd80c371099c32f1abc4eacd92f2a03a9b4d4cb4afd2a58be5a17fa84cc4538adb27d2177f3d06d3d1c4ca3b002c9647845be41c3092db0a01c250075fbd632c070cc9ecc87751571b4619232789462034dd2129c995cb4c676e5b5103a06290135339834bb713c8da2700bf4a632ec734665302b6338d4d0d0acc3174736e9b3010c2ab5a67f276cb2375889578fdb7dd03ff6914796fd630fe677c3c07c81e4269100826d7a7f5e6676ca907fcb6fe51f472f6489128c6c82a21413e93c17f64c804c8fe4511120a6597bdc32206d14bff72aca29f5aa3c4563d6c2781c952adbe82882a6de25fcd1d30f4b0fc7ac2692627062ddbe59b34ea6d6c43d7ebcfdf034d0a6d8bc4d381badeca90e4edc7bcc966f3098988bf552abae5fcf0c479a06768eae90cd75abb8cdff9ec0b65aff70a803632ca5c70afb5afa5c508b33e01cdeade52a437372b4afd1ac565f016c10153eb90f2cae2df2551f4db7c16544de5cb5ca9644ab50ec3da4ed13fd9df80adc73caf56d49dc2b6bbe74f9b1b587359460c2d2d82af7cd9930e791cd0dd347085067b64fbb9034685f1ce774d96e61bdd097dd7523a9b4cda78707b82060875bb5b22463accc7284a7e90c320ad2e081d07099ae9028a306723e4993ddd24fcc0c2af47e704a1f80f284480da81fdde36d492deaead166eb784c9377c3fec1c0903edd110f981b8d2a5b016694394a018cab55abbc76e31352f9c70f547585c31ea5ea1bb96217216402137ca70a541abc1cbc00f067735561c49ed491c770ceffcc3f2d17e3049f7e857311a6563a96ca41cd6aca7127e9665903843274c87a7d821133c6cf47013714241e028f043587117814559bc7c756c096190aa8d2042c3a43ab4b2a53dfa109e98f26d5108cc484d8ee5bdf6672ae302d962c87382d7ee3e3b79594fbb8ea8219523283388327285713067c3f28f6367d498b2debf5496a3c9d2b5daca724a47c64c71412d6425bfe6a085a0727e1b2773a18199d02ed43159844f785ce55a60c5da9b50cb9e4c4a2d95545ca795177c22beabf84d1073d59be5e96607be8b1e3448c4a5d547e1241a6be8b8018b27dc6a40571b6f42e93f4e9bd14a06b7a9b44df33b6d2cac0573427629f9e75440664682c419a172399959dea7e8a6e0b41c8bdf9803920ab8c897cf3cdf4793fa68db01fb5a37a4a90d654f8b60f163a022a1a4eca540103d65a90ef1955d9f06bdb394185315b17267e689a63a6af9217a8cef68fe906f9a42745818402cc796ef349b1eaca91b530abba7d14ae01732209a2f398850fa1c41291209656cc5bdeb2fbcdc0d4d8ff91da66b27b29433823db1b29bb827a3c88a55cf4268389927797453ce3c81506642cd13f65c56db54edbd14b14d3a0cff64b097284aa6a4ea31139480a1efdfcc42dfcb7ba4d70bb0e60f1f7d3faf35ac2577f048bacda473282527ce2d5eac5a23f6c2a99bc30510daf310c602a3f7edbce879dd0554dcde9e6758b7b49c05f696f84f251f4f5731cbf803b98a748eed037fc93f828c81f4fea2fe5fbb6b1b168a902aaec4390b23b250504bd04c4c12c371cb8b76430c11373c59cab7bc3f0badd2fc26b06899b5a62c01bbfe4230b833fe9b295f8f8133f4fdea6e3173308bd97d2aa81283075eb8330064ec83ee4d8cf21ba85ec462a0aba2634cdb3781244aba0dd06e3de8e276acdf7cf3df63c3673d4320d086b575acb5dcba5ff40ea1cfb1da95d5b32502da241c4a58198295e47f9fd4766d8b723919ee800c7df82d70c157b874c9da5790b03cac1fdc8b6a7c24860602c67d3c3d9965560adccc803fb5281014539009b7a5d66191820a5043b97dd7a35da2901b37aae452093380cbd95c14c0e984b9a7640788a5530f77cdfa3c12b4529523ba1643b66c86a5594eb958721d22136f26ae422d8568c3626ca17ea0490b4ffd5f5e9423e0d342d0285308218fa6e77d5795029f6741edbb346c570eb321b4a777529a9ada4c279cdb310fe84e620858c7db6fbf65bbb28118fdee978e640cd2ed9fc1c31f45de78949a3fec78b4993fa4ad01fbc3c674a8b0a2b56d9f6e7d73175e4e60a5ed2a265d5506dde3e39f5a0b7092dfbc5e703481171071351e22515340d053b8ee95dc6444b69c8a5c9f92d898f2aaf547870c6f4e093393199320f40c679e4423ea3da0379c2452ac8459a37f7128b9b5d3c134b2ec8308d3c4848b12a0d28dce84da880212f22db272a8706424d418f6f72fe7fc642d932ad8d1fc5c018825f955712e8a151f407b68346c164c1d3ec8f2bbf0911708b7f54b00af068430a54745fe42f1c20e07aab7b1ea1b284d90b2c1037533ebb1ffc03ef0192db4cdcfaefc71815612169b3fd599b5302a740188a2f19c10170bce81a8db8e8bf4b0aa97a9a9734a9d6d2b9681457e6634818c11a62b98cf5f31bcb08b45f833462ae266034f4e93ec5b8f0de119d32c5ac1916c6e58064eab35eca7c6aa4a2d2201b604a4e52b06b0a32e6f90b6ccb0b59065dbbf8f7cdb06e5bb696cbe08a562685bbae4782ed8140a126319595a54881eb1c6c15845b4150b071a03713add5a0d808d82105e1e8ba38caaab389b35ec297e3973cce80d02b826a8dee078a387b4fb1c6b1471770f4cf2245a3f31d078f4edda1e5282096476c6ebe1395619eb960cd8c211d8a74968f3264c1bc73388bc46f011c038a2ec8d182d1ce29e501182d1d458f581973780dc03ca6a4312cac3d5cbd230c0b0538186a873665b8f3d83db71cff6077f616d226c91555d40e790d38e07301c046afe1f3f5c01b43416ba02f02489aadd0cabfd808bff459ec178b9cbd29480732ded5bc04b98d56134313f4a0990ed8407634ac5b15db117f43993e1839fb48749a76cb1b8d6e4fcfb05cede96edf2f8caf4ec29df7037c1dc72421572bc3c0bb90b89c524d339ee10214f0aacdcff94bd1d6fb6342e9ad712b68a0e6a8f293ad89044079391c2c1cc504936e34d58065c8b290b10ca88bd3b00593ae2198194e6ea16472c28673990d24696bc6e76fffab3cc6e2c276fb4ba2961716f1b7983362047816e6050d7468c88665e7c412cd4d94d8f0ea48af2d9c6b903d8529f5e34257671a209a6902c5ba5d5a028437deb361091310a1346181bf04c5f9d60a7bc373f4d131747331c725834d3dcb53bb3db2f643055cb5eb525409b92ac2a2844e9ca9d16be71832130cdd0509247cc83a73a0fcee829cdd1de208584dbbe75d6cddbd5f7377d74bd69f770fa585a0a18c31f5f65042a671decfd22080d9e1b0bae4337f63d754222af12e899b3e597bb8cd9a462fc8ea5eea4105b129474409a33dd9e21e80339a998acc6e2b8400920b4f0b0b4d05141ae4d78fae68f18abe9de81e8a06ba7d0e1ad61c474e15c607ddc08f5829fddeb55c304bb6d739bdb0aff709d5fa60f9c6eb154d3ece0139b50e4ef614b4a9508b7011195c069a1a6f2bfdcf2c3c62e366d17b099ee700f027227b0f7074d781a03407421c892ea71246bdf66fefc2481e6fcf50110c9c2f3b6a97678edd4bdb5d346f06d284830750ac1852a4d8f2bac8b49b2abc7c05c9e52a92f53331bc411aad7079d75f978332614e2ae874440b69d443e4979b798a9ac5e3b716db55f74b96e5d472aebe5182c52158592b4254d9a27f3975564d5991a4553cef1a5123779c934d80c40ed5275d62dcd14be758783a9897405a1db42ea5770f4fb893020608ce9306f24a2c4537e82ce4da84249e6cceac279f0b18d38407f951fe82d59e66ae6b26902ab13ef934a09b481255187df5b62b4e4d3cb5be5ab8831006d1a1f24b72bdf54c438e04be78e61686e56f01a6d3ec3878e9281d0391dbeffe054571e0dc27ef7c1befd3b40ad4bb6e7cdb20f79a4c3b17ce07ee30f2e76ac8dbf668e1471227aaf836012b0e7bec73ea0fa473ed84dadaacd9146065b0a6cb6d343d6c23e5541803ad9a88c31321900bfe127d68a74ee067bd31f01b80a25b1099032a50c1d885c37f9ed41180e9f6e1f3e4a0000c272c27c1a9ae3743e44c05983e84dae73a6cef81a98c405254a1f5a125369d3103300a6204c0700f346c8cf67e16500c9045365942838ad4808f0173443820e865cfd51d19f8b6f923a238ef8822d1553c5606cf6f05ac4bc802aefc1c84e61e2ba4c99e34ad35131bc9f399022011f1cb152c7856f3c00219d94cc4776a704d3e5f014fc100611d0546971bf4540c12b48fa8a583bb562a29bd294f710e52a123fa38bb699ca71a4e390d2aec93871ae840eae45762af13b7b63f64e23d49b97ad2b269d58264919ecf12b6a799f402496ed882beb3609e9971ed68a6508517e874f7bdc11bc2a650a8a83b8e43381da97522a38d44627ad8543542acb793c14921232ac4220f2cb2445dbfbc57c6a3f82de86d908a1d053a38ea22fe4905c218f11c728d06852ca95fb2cb4e0c8bc9f3d6c91467710310072c56133796f71111074940c6618c2d264b9df70d9c986794d4536b5fd341093d1e873c2604c6572639d0070af08657b9882b343f6fbad1207818debd20409ace36328fdfd533fc29f2a3693f4c1099618725b6c0a57bc621ab8e838472879738da65c985b27faeeb5ba80717b4058463307f8099de0c2f819223d9884d7079fe88b425ff5a17bd5f5773435adc36268cb9ea6e638ea58ea985a44297e78cb2fe188d9ae62f35b1e9ad1c193a79c5b8447347c6158f9c3a09d0c2458f003feb50c66dbe2ac68da20e2e87ffa65fec8bed0dd9f67f3a641e9e573f3608e9e4306f111fd8c0c8f82b4b1f39b3f403f0dfe391bf53f6252689c48f16cb3b9ae36e7214251427ccdcc3255b55f4df33b8b955f8d9cbd90a44ada1d5c4c4537d7ba306ac5e287bca2f474499b7305e96d9022688ba990d62ddfc224dc45e97f1f41397e1fa02df9604fd5f42e7bf5a71708f66a67a88d9f8a849623016399115341cd3ed45d4ad95e5ca63ca9b0a245bf65970e2551084f9c2ac36e655f112953230a7605344ccda6116b5ada33b2aa5790c4627bbe06fbb7affe16d9024a792b2ae86fdcac4fe5d6c394ae148a29609fdd191c7d759098838279fca92f8047c7cb62dff8f45c6c9161b9fdef9449edb0dd5f2358d3ea8f881955c4632f5c86710e16b552c5619a9f4a41f34bba7b241049e58fdcde3473581ef08a4e913c2d4e913a5ee8c9ae43301348343e5c5da564c6e197f0cd80a3096ffd0d020d5f08250c02eb52457334905b579aa0378ea703f43ff679c4bb319b78cf4825ca921fbd08621630aa10ddb2d75956ba38da31c3bbb5c3e8082c40addd78552ca2d33e88b0eb6a702b6d42de284a013ca94736e613b9c860894f1e34ed1f3cc7d652f686d97bd445fd85f91182635eb33a128935791c92976383c0caffb3984f94fedf5ff90409e43f142467c00dfd716e641ecf9024ada125a6e0aff3a8b3bfc5dc5026c4018afa2c0341edd13bd1a634e47e680972250499c1d021955fd24f1b2a4e4455e6d840955280596632596956b01da17a07ae2d491c81bce0bc4df638373fe15bbd2fcd004676f025c1f7de762b5360e08f0234a22800e0ec12cfffc38f5fe8a542a878703c38316a26fc202b3f6fa5916ff52238d2e66380f5c3d4f5e2a898acb6a8fd623e5586dd7b1e77ee0cfae83a031ea9220b10499563d32bf1aade08fe0e4ef42f5344d17bff24b8208ecbc2369b3b25ca13adb8bb24e42ee29c2c61fbbeec42140461a697de878d0c2f711eb9ced34977045ea0cfff384180991e50cf536e8658a953ceba37638f2db9f3794456b20ab47db2473f262890471b09b620e31cfaf8ea6a1e1109c8ca4b5f8ce049303f082f81b4599e2b2c5710ebd18562df7ae194b5f788a6c2110bfbf4bf9acbde15af6e74cda6ae07ed72803eef9e2cf5c71a8ff83679c4ba846d901e1e219dd42ecb90e6cded7ad19c63bf7d5cf8cd4d1aa1839620265888cfd46b5a608e7f18c7832d3983eebd2d3acb6c231a7286e2a7a6dc91c790dc486f5cab991406b7b8926f63f94880d4aa41dbcb334531efcf715cf6ff0ca35ee22d983f7eb1bd590f27f08e2f9f827d9bff9ffdcaefafd2af22d7ccdf47300107b52b0011f73ce857b97041556cbfcd7ee6bd1dd9c445dd5b8636bedf3ef79b7e63a5adf38670f266cb1fa2722342481f57146d102ffc271104c1ac4415e87f11d9f4801fe156531fb61934fff4de9c5d24af8185171f2a09312f2f8083450f2b4d45c04109ebabc44cffa0b140218399a8117711dc4d35d4cd8a30b3e69a4f5dcbb490f8cb92f204c8c7c8b7a4cb86687d3020db8dd7dfa28d2ed82b26ff48028002d50fc418e05f71da02aa0a50c170a13bf52a058ca4cc7a0004757b1f593616e1a38a713945947a025aa2f6153c682653d06acca546a8979d2f2ee74000cdc4749ffba444c59098d920ab9646e6932c07189357942aa4f85863c11615e05ac6c6dd68c5e6a798ea72bc2e56a365c87a3a4a57e60129459a20ae9452444a9dc1acfbbbe48e2a837ee7ec421e54043e95c93500121b4111e4e96bd749333cb0d5c8e4830942323ae087e83a8cf9ed8abe69022b3b5bf02e3e2890cb465cf0a0b6c127f177a660ad6609b8a416a353c53a8266b4b02e04005224626f5714e40221377a2a2a6c331b21fc61c5801837e935df1a4793e0e29740730202468177f20e63d87e5de7990980b9d267bb5ac596b9e39dcd3f4313009b71a971c969e84f4faca50d1dd20319b4d553b8025bb1cdf7798f5ef684496d772ee5b2e46d7738475fe449df78eb1654268d02e4783e9da08ab7cc04e59286895af07af57e20139448ba09c4256da202491b6baca7e12fae0ef4e6a2aed8424be4c137210eedac116bf8ff67c88534f8a873b8ff92c2ae8c00ecbe59f0a2c398fc5b04f9f5c3dd93008e4f183d2db4d07c7f3513fca2a4cc8ac9b8ee650232babfb9c3959b522e9cecc61e480ef71518ea7c180d220a65599d51383afd23a6c0778c2941dd22e9dae658e255e9904a4be91af83db11eddfce029bf25509253e90dfe3a7e678a825744e45414c5995e569a9a77f40b3d824f6f1d9d70e537647f95024829e2b5d2b0d0458a1949e599c0a773988204433b512b229ae0fe4b8fff48e57d4bb01825a8b0cfb287890e2e1845b017686fcc1a477fa9323b5fb27466ca0f64ce4c8c0b24918bbb5026555f8eeb3116353d99d49f4c76923929ce96c7540f9dd2394af6c525a5547fc5fb8072c9b400b2290e5e117ab052daaf55edd60ed2e9eaf182a5a20b62c24261a9c9e983c1b7e0dadbda47fe7067e4a3edec58a0534aabeda8fdc5d40ff69b1d29013d12549d592b5425ddcf60c9385024d37f52157084f45b7044ab002beb443a2c66f519d2736fc1e155962418a7af1338f3ebf05cefa4586153a60e6612b71d819a6480021959e6d489270a104d620db371b2d88cb9d261cae9774b32db05a8153c8e33acfdfd68a04ef274641e386d80518e990d28f75bbf29c3da88ffdf04f6567960f1b7ea97e503677d9a7b08be79f8565c0583b27a604dfa15e86265144f58e5a81c6c9edc36acc85512d44ae89338dc31e771081c6c92e92b4a0287c24e978dfac0159574ec5e0d734beb166a544afddcb3068042114202611f414b83709d6e2ea8437c7f3aa7d051bb1ea2ab3ec173659c28fb36ade99d25eef7f79092ef546fd9069f2f28d307cec99a9adc526e08c1d81d882fcc99e0043b292cfe916b2e0ffe76b9bc4f63711f7e720960fbce40ec0037b1e2bb4a605aa9eb611cdec05fc6799ac20fc87ca7d18da32ce19c8f01654830479ca5a37912ecb7e4c9f6e6622878f25be386fe2da9c1c1fccf3f71ac1dadb2b4bb6f7a31d5ced17183c55f25a384b6bbd9008f6bdae734107562b45c4dd851b768134a3532d778ce1062eb3f1d91c2511594654f0853b60219f923e300e28c5630e5cd80cc4d4c6b15720d718d9dd3dbd70f462b396b10911b5d405bbab312e70ece067ac183398277fe7d86424411525d2ad2ce48a03a29abc3a73bc662925e2d0c39d480eefe569619a081113c0426226c394fbf056bd6fc66f380fe1c36262744d44375c601c65bd264f4eaf073e2c64cbfa14e725fd6a297b3e899df87d2e77a30f91aeb07cec890318d92a8af36468db04fc5c49860cbcacb0b22685ffb1224436f08574c90672db8a4003ce44fbc4643581e2e721903112a19be149684ddf62a453bf75117b3d93646f820281492d79f836813c63eed8720d16f257240cba6f38033911ac4817fa06d49a03cd7e4b96142b381fabe7688f4778f4827d276c65ffbdc2ea16c34661e021f51dfd56246a5848c006103f89fc044656d433583ed82576c3dc9cce09f53b6a7435654aee451d2bde358fba22f9295dba7cf8c9ee640bf16579385bf0603b918e1131c7003daf1d9fe0af0d3f51b25cfcad4eeda3e892c092b8b866c6d9f4e9ae84a037e2609ab322b25907a401d607e7579e695b042435bfc3717a1944efeb75ff8d3bcc2477f0582cb5d1237a5533968eaa4d8c62c9373ebf2cdac716d1fe06503f8642cc1d61bd4eef5fc074cfda02e4ad1ba44f0b4f0316206db4516aa0a688b030916b56a12f388f2944be6a4e46177dc813ca07ea7a767e48764eb14cdb56cd3de088aef88a9173b569bbe6852470afb016b84c0a644a7c53b981eb465a7af9b97b34f893f87ab55d61d205e99bd19b00fd63445bf97e05eb762810f5f9d29cc7a8c9c3126928c57f18d3ae6064e8ecf21c24719bc06bc479ae8bc46f1947bc9360b9ae7abb36b24132a4ab6da253b98b3287e4a9fc66889e60a505b95c19831581c577fc0764beaef23dc95fa0033ed066b6640ab4f680c2b31ab9ca4b40bf050c5aec254c78f78c3a7677c24f92f299edfc1bfbefd17095a415a073f120461709af6ff1250336c055a0c3d562c0d6edcd7c058380b933fa838f797aa9aa3ed9467a6e7c70c6eabb44c38c88cf1416bccd585ad6bb476fae0c2831e146a0975305f615cf8bb9305f4f48a0941da78bd78bcee8bef8d7532db404769a4db0183ddf90a768aa73b94f8b3ae69cbf0b4384fa7991c577284cb9f3f43194d4833b9a6fbbe774c0f9e2f6bc37bec9da3f19d495e4fa489fcbd47a489dae603ebadd506a72861a3da8c127b269858a85125678b308d5789d8a90a245ac10eb835ea5fcc5d093b30566a454830d92ab4a017fd4c7d22de951eff98d448489953c02e95429c87208cf74d660566a030a581eaa695ca076fc2b32f98ef138629458afdcf08af762bac6b53ff63fa88f1fcb7ce69cc66407a290bfade0d2b89313c5eca3e8bcd810d61b958eba5003602c2c5394d9fe1df1e4257383db694535d41369e0ec07a611864c1e0de90e5ee70424a8db325d12a11db2cc33a2412d8a1c21f604bf7ccb66ba6385659a6a1f0835279b97df656b8a3073aecb3527a851b82411e5e061477e92ca8d1f7137a838b8967300e2b6712b7ec1925be389659f01073fea229cdfd9c1ce76307d9a65c7e56a8779dacbf7c0e4680d5200b27f04158e500ee9f3154c3778184b2283ebc145088325101f3fb7ecec978b9461e235eb43823c77dd1d7a15ae317daa1057a8c8147964d7b8729d6049836a7c253a288650cbc1d6c04dcb5764cdd2999d96442149879c77efd4c1c45f13ce97a12ef5c2c313947d4f1c490546bd12a2df99618fc47d59bbb400a7126a3f9e87be4f91cea3b15cd66097fb979cffb18a87ed6ac41e639ea239a411ea369cc39b05d823145aba63205fa17bea0aab0fa988d795026b8c1c5fba27b035aa4783f5c15707effeec09497d5b76333b6eb56008619a85d66e0eb8b3bab7cfd1d64524006e658225fdcb81eeaf0ced8022dfc8a2b006324a8826a3b1ca560fb4e8b4a545f5b9ff63f49112e29bcc239a744a11e1138868bdeeb1d1b599a617466bfaa12f40b77c9d0ca94ca875e72f7aa8afd1047e74204e8c14b9979ba8d57fe8ee8188294b88c18a1914d4aacf5111384557db64b02fab1e80f5b32408795f6694a65e81adb5939c2f7e3a68815dd28b1b3a08bfb5a6343ff1d19d898f3b1f921c04a422f32915fd3e7603e5a16ca3218947cd0f8ff5f80ac3568cd8d7767786354aa0d47ac3f25ffaf0b181823a0ca255abf6010a4106dec39e406a2cd9223ee8063e24fc5c0034fb751022b667d012bcd6a1d33f3535a70e9161b2e18426b30819e44d963763332ab85ff196d4f717e67e48482e0d23887978252fa3501748fcdf7955d2c269f04496b3d2ddb126b02d48b842234c263304124f8b180133c79083dfd00515115380917da0fbe55cd751d20a37ee710f20c933febfb06b21a638c43526689bf0548570a5613573308e7db2d143166671717143c4b6cc6e69d3793984a139c28d4210f5d58d1e1c0ca1f59a25b9257efa61fe9d7b8bd3e9ba6704790b506743a35623a0b0b5b389e0e1af1e8465065b6b5d4b651612fbe5a306c460bc094e83ff6edd8d03de30379e4c726367502068802f87260e1af16adfb17bdd4ab0c56a86b54cccfdfa6e8f351a6d40d4ab254a04a3d57464105b11b6cbe179b22bc2b423e3488efbb654a8071280ac230e1b4730a1a7afaec66cbfffefc99c87a77779b662ad405d12b1c468f6e04b85c62d3fef0d3e3676ee2fcd005d0ddede688343e8b20aaf3dbcb46d95f94e335f8147225c0a6e2bce62cee1254bd4e63c81f328e46b560b67b3e517a196596c774a6b5d6a150a16348e62801c44d806f1ec2a161392c46b22318ac51cd7e9e5eae455769e63639fb4ecb59147ec02b32a8c098085ad85fdb4850c9ecbddb44e6f72e9d522911ecb188d1758710be11e469c8b97b8225710079e0a0377e48dd74ce16bb4d326635e275d3f8a4ab5e32e8759d2484e2c2e89111e36ca662f3a657c6ebcc44a8a6d7c7e56b7eaf335b4035afe160443ef789b101deec297b7f721d80e57d4ff7ddf8d66144945fc5ea12d5640246a0e19b231ef0e41cd4439e1110d3846c6c99e1291f765e61ccbdc4383708aebc75fff45ca22a08b696f656abddeb3383b605695e0e910b0829307ac80eb687ef96ffb05b07ab014b0592c81c8fa0a8a007eff3c42b7c4799514703be59fd3d5dcd0b2191f4504e5f500967a60c8218d3cc92d47ec1a206941936e7dcdb568d85f377fa039cbb5fbd79deb6dfe6fa01b03b5963a9c2a8dadc52196effeee3ae7a143f277309d5ef7d42110a0cdc6402adc0cf5c9a13248a91025d9dea5343d151ea13a43802ebd82cdbce69af4ba0736b27977d69fb9c5202b305c61cf73d50716ea7326d9a012fe4fc4bbb3bd5759d4a8109e04967b40bfe467c220f00fc6db7bbe87d059d12b42c3e9dcac4b9ef1326f84f69097a9f3dec2cf8537a67278a812ab98961d8944d032b36bf91fa2d84adf8f57593f95dca5ff0bb793d4f4b582817921c8c00e0ac62cfcdd64d71810758a6f3ff930a2f6048f4a0bcefedb50eb779c22bb8574121caf0c18f420e38f935ac62cd6f5f52cde18041d3e5e9f8878e64ab6f2e6479cc58159c957579aed50b3a0e3ec3a8fea265d03c2073419522503999a5980394e91dc8a2659ac3b4ab0bf88e4430ea72228532a63d8749a1b1549712a4b9b348286e1f6cc4474095cf9c87b9f18d0e22eb5392392a9b1436fbbadf308ba0adfca4d9483ef86be35d6ec0135604f821068117edc85a147fa7cc314411d036c61f6109a8a2bef51216ac19755e9755c60887827ff9b339d326703f97b98923405709c32d0228b3a93824ac7a651394440c72e2c080f900ab345a97abbd807c45a1e9f00012f71013070ed58bbc2363f1b5c35b8190e9c9baa53586f4989a75c06e67397ecb9d2f9eb0489a4b6768c3b14fe4b38cf3f0d208944167c1590698d38d4ebdde64b56b4967a84c8eb3e9a119d6b94617dd26770407148403eada5e945c30ceb29a0ea6375fc1a46542d5a72066ae2d0a5d8101b7124c3862abd8339f2d2546b2a738568a0e483df57f28a1a4a6fb18140c0baaacc3a08ede78aeb64d99bf93c753a7e6dd7acadfece5d3b282bfebe55bdbddb6dc52ca94a40c7109230904093d478a9d4b1e53d9eb0b997a870f05f6f620cfbf7a5e5d3ead33c84a6badb5b65afb436af8c925776bed047da081edcbdc39647914442d3b4a6291eddb5187b53f615eb2ad3fad0bddb26fc76b39eebbb99713a76d7b3fe742bf5e88b5d67608a459f6bbb0856659eb516bad3d7140cc935dc21efdb81ff0b4d6da1a46e99fed63f24c6b86b5d6563badb576c4d65adbd67e0c121e276c65a7c90ab6d65aca7573ddd6da0064fbf6559c8e6cdf729cb53766f6a7d090b5f6271425d9fe9c4da37e51d13ed73a48b06f5fc7be09edf2b7d65a6b2dc771dcb5dfb01a5de4c794344b06354b4affe46987a7eac1d9e7425505126bca866de4ad591acff296ac5fc11c1d6fe55cee6fa8eae1719bf94be5a3e342d593fba1cae787b7e44b9186f24d34d480784b3e0706f15627f59566c554c145bc25e52dcb6f9bb79277ad93aa947236f397142faec91be94292d64e0afb8a126cc1a914f37b63f7622ce5c907a583fc0913929fe54f267247ce584bdb304874ca559d72d2f20a20603ad4809d5e21f0d0153e5d6856bdfd80f485cbab183f28a99e04df1ff1cb930f53789b60235d683ac2570142280007932b273128fd092488dc194046bd907e39994c64eb72c174f60064880e0939ed92474c8632c57203f60f291521a326ce0fd89b27064c9c3a41bf826b13dca35939fe8a408ebfa4484f405a087fb8bb7b9f389bff046dde01c483b8601b736a1512f4c2b220448ac0849424c586dc3562940e1d2a5c75db3783588c282677a8dcf14c75bc4567467e456cd4196212f36149d7793266e0710e358b7ac0072cc5181598d2c045789cb150c763b158cc8711198bc562b1988c2836141b92c56232a2d8504c46141b7a80bfea89e9a5d87d4f4fd6d012562c765990a2873ab1582c3683e60d538849c48c29116a16a54d6a8d758bced8fcd8d09218152645b17e8930e4846866c4440f4a34a4980d299eb4e96f797dfc7ff8f7f8f39012348147b370ea59260de0595fd5c3fef4db50f584033201e4d1ac9e1c0beaf030a159261af0cdbb7241df934272a773f732af038b78ab6d591ef1c0257b10217ad4832cd32eb0e97fa6e89c0eb8fbce95748b4ed80cea167d0fb6c3a77b9932baeebb2ed4f116cd79c06582553d3a9d30060b1e250d48acc93d54527420a21b8460011948a0980dc1c219634c39e28a295070444c17d87b29ceff5e8af2f4c3f43fdd7ba16a863ea7f75e8a52a41f3885c0198ba2b22f55f351351c2474a159314fe0b6788229071f732901941964682cb1891448684a194a5801441a380421d29f30a794d24c55984480fb7bba09a594561ad42c4a6bd7dab5524a617e4aa16696536b8f7808cab4831b4c68c91ce2e1874c1d87969991ad73880bfd5b680f8d46b30ec35d6ed775ddbd1f44eee0c8f7be90f66e7767153fda759380dc3b4494eff56e772f18b84ff7defb40f7a6bc75ffdedbac7befbdf7de7befbdf7e23782f241852a1f9847bd147b1a77a04cb3eec5f9de7befc38040bc751f0516f1d67d133893c89bfb31f7de7b2f0a75efbd5d77efbdf7deeb994cdd673275ff752f4fa6ef69cf346db6bd253ceffbb8efe380c81dcad3efbd9096ed7d10b9936ac9c54bf6bc9759be20cba31db864cf3fdae5f21e480b9e77a40397ec3d09edeaec858175660cd39593c789244751c1bccca8f7be48bbfcbdff993cf6bd7f41ee70ef3d90dcb9effd84c99deebd9f30c81dd37b0fc45ff4bd0fe2affadeeb787f0ac77bbf70c49e0e0aa4cdaaaa07c4bc11fc5154f3a50f3625e1fe0ffc9ae5fd046fb3bc21de17f11e88f741bcf73058c45bdee7b46b6a60b2bcf7461dd9c340bce5f5536f9e589062d04f141ffcf5653022e2f0678af4bdfee6116ea93e0b52acef03e6cb972f6988f43d9f2e1d35fee40acc2f99a3eff27402cd159833f83e20ddf23c69b349d8f33c0f753ac19c4e30a893174a60da3e18c069c33bad7d5a236f76aead17676baded5c79ecf79c9938944a08acd878a1c308aa8862454647a0030ef508dc41964739dc9ec8e18bfa258729ea129ccaf2280725a0c8a1091539dc00460e347818b8c6196464a12dc030533810783573c112a2c66209077c096ce38b23c1150d4ac31cd83a0d8c3a81dd019b90e80ab0a9045509e6b23cc2210434e0e084cb922d7008b24cb0cc8d3a81515f7418f87485171ca04279e04091fd8103455017e00db23ce2000e1407ac411a150c1cf3029701be91028a04ae51c5f28065a4780bb00d209c087cb33cba41079b03b6591edd10a34f70cc17b702abb4d429300751542eb0064a780cf089088b1dba091c93e5510c893fc1305dfc0aacc117f6088c4fd05d200931e148d894e5910d485020302acb231b9cd415e02fcb231b866c88297d49dc6967edeeea369c81a5deb00d246f6289a03388a031388389c5e10c255f9c0143bf00afb4a460c98a0e2c4931b454822e968ee8c0120fd6075c23cb2a4b49281718ccb24a973094923c4a972890e8529445170fdcbaf0e05dba1cd9821f8c8e0196b1420833442002333ae0841931ff62c69121ca10838932523043192400a30c222e65f0d054cad880122f34087b31f024db8c4bad8280cb94b6e1559655b834e929b8d010c405861390c1c5b720a38bd68263b2ac420615940a7c23cb2a64185118e00db2ac4286105830075956212389cb702acb2a6388e14660992cab8c81c50a2a1c86319c98628c24cc186388aa658c1d8e601b59561923c8666fa55db33caaa1c83b03a6365dbe723cf77bbede77022ec27314629373fc64356bbe4db3a8124ae4f95409b61fa859300ec293f0815ec11a6459050a24f504986659058a18cac05e17126018a57a049fb0d0237c5140b3c0d8895ac32611d426f88be228c09d1314ec6089c0a72cab2069a0bfc03032e81560ec02ba84515b501d302acb2a4f5491ad10f6e665d22de9a44fa801ca0567c0d47c135468d7f83dedea9f474a96745f9ccab24a145f649ae59192581e535c125ea534f286a77b863313a7fadbbff2d56fc3b1629896b7ee8fb64e905afb34d5acfeb13e17da5a2bae2f74f719de7c0f467ad1650fa645b30733ce7cc3c023eda413d98399d983e9207b305ef6603cc81e4c2b7b356262300c0c2a068542c59c3ecf33d5e8bacb71d6c654eade356ad4a8316b481953a366a039279247804f27cb7c8125385feea203a83af2089c00ae085d4ec6f102f0dd9c72a0000013026ec5b06a4cb933e50d08f982f1818c071ad8dce860833005e2780e6a543433ab3c93a92acff76a3e5c02ccc1e3c8f33df093f922156ed0c10d1b0d3c90f9c006083558312d1c020c0050394e375f003c1c534e2782003e6e049be77b3a3e992fb0fc3a00ea7a7d388ec0f7bacc17787e341eb0ffa0910f7ef5291b534592bf3ef87eef03f0a797687e6ca59af055fe34a1d009ef428d24478ce2a14a8a3e34717ab0434d3e00a5d84446249bdfe795ca7dd62c239fd19068b93fa64acddd975cd543f5333fa37a32f3525439d815819d110d6e73a522a701ff78f0abf790629e8b79a48bd4cde44d77b0ebe5c67b67d41535eb06ad5d37bedf03f0bd75e35720cb5b37bec76f80326f034a510350e6bdf9d978d819c9805d918dd0c4025c23e4a898381d038e9d94dcdf41c881e3cd181c6f51ce018f57c84aeebfb2fe2b7495dcd865e244eec83b34bd693dab33535cb94b5d9fb31cf7418c8b1b67ed4d0b50661b5c50c996a3f62b6783604c8646215fb6dfee7e9f032bf042f60a4c100a915111155acdca9666b5d2e92b98250c2512268c25d99b4f20cdb67f2b0d01d7bfd99353099655b828cab20a174ef2689a92fb65135956d9c2963dd4376d338fddcc4882e1a408150030a0644f7a31df0bd9f4151c8be47a3baebba71db7f3d2acfe157861debadcbd33342db6cb79a9c1c6f80be3bcb48bc69c7678dfa9c00a9a402293e3824f3bea9fc06e08ec9834abab2fc05c18c78582dc92b596039a162a960b178ecbd290bfb8a4fe9b3780d91635a5a49b0cf278af704af5b483eab86d012509c90a91996d59ee7d6a7a7ab9b760bdf6e97b38724fdfeb2ecdff44e931f3abefefb7343198b7fa6742a7ff353ffacdc2fcb958f72af03e98a404b126eca7b5ebb81a37426d8b2556a88cca28f79dbbd7e7686855dc57db5550e695538c67fea7863f74466b4f33045efdccab425afb91ce6642a9f2e69e821c150e0a7d7f1a7233150a1c52b39ab3526b177785f3d30e8e7202a879a159ebd5ef14555515fe4c517a8bab42deea27153605fea13723ab90da6046a2f4a079d51b117a71a6e6475a13deb0cc52344213aaaa1491bc3813d6d004059659d5bfeaa7967ef734e498e068edeaa820b5abeb44802975f1aebf54e07bd091d5d3bcb702939420aade538135af7a29d2804780449a074123352fc599fe56a0a47929ce8041aaaff99efe07c19a23ab9f79b09b79abbf06ec6840e94f609aaff97ff055f5572faa6ad800232a1ad008cd4bb1e6089038f320780448fcf7e647034af1c1ff999722081af99762cdcc83e0bf3c3900489c099394207e0d28459a951d3bface31d1ac934fd02cf7cbaea081535404f31ef73f332fbd65df86aa50eeb0cf8214552f4f0990d9864f562f4f3e56dfd3a1cfe9fd762ffd55f33f33bf7aff4be4af9af08746155e225f81d25b7e894e700efcde03472234734b38254433aea85d9d51bb4e4cb4abbb343e6097f974a2dd2954d5a11e866e62599410d12f985b129331e188723f6d897a371cbf7c29a532d9ea55b2ca7213a12a9fd537408aabf751bde7d4c115e8ab57853faa5f853fab9b5a5155b75275dd0d39269c2c072ccbd019508886fcd50d0d651a131e3747e0fab4e374823dbd05a9ff7d0f65b34ca83781e3cd5ec78486b79335abef955b6b565f287776ad34ab89a8d7bc882de5a62cc05c52371bf2f73ad6a7f101d7701c92edcb9f362442b3ff774aa1842f5fbed01a18974a93b87e39de2b59a95d1ed6d98cc676c3a68568f2a8aa51ae239838a82b954b6a17006637b02011b56be494207599a1cd8035650656fdeabd7d8254bf7a0f53b72a30e77fc29fd5abc21fd5b77a5ce5cfe51528c57bb958b3fa2fc82d695687aa97e2ca5381b506d6a4ce28ec8a6a8d0635ab5528dca77f9f724ab858bb96b4eb7e4b1a1f300dc721b9becc7ebd864468be7e43eb65e284202be26066dca47b851312ba3f72c0bdb43f9a78ea7b380a09b33f17c40959e1601c8c13ea90f00b72e088ef6ce2f445f2d75dea56ffbd18e92ef55de2845ab01b5886ca10f21bf3d77582552f4f2ba842954f900c30413f517aa87ef5fdab177a3148869e55f8536fd56661cdeab7302f8628da01091dc4c0a268032a8081053c4c399222469212c4d5afc21bcf1448d0aa4de5af5abdaa015254fdea8dac5e8a63379b097f80c4995982b87a245354fdcc87cdd1843f45449a5f8148aeb87a151824a423ae1ec91555dfe334a00c45449af7b84ff5004efc01126964e0449ae74e3e34342ff4e2cc7327950f8d9de57e997b60989d51b26579daf1c4cabc550556f9cfbc919f282c4891e6673a51e5a16a858e082c35cc20cebc8f0f7c90e2c50c49be7cf922d63ccd1ba1093b9ab7ac6c8a523442f34768c2242588344f131ab11828071fa4783183100c06136742a10dc4d5d784485ae2ea9e7c7830c416a326427cf922ae429535122dcd5bfd2a508a282e8c58b62c4f368d89038216b534ac20a531796e0d2781c76e769190ba22232a5668de47c110720565152d64d97a9938fd1fdc6e6411f342278f87262ef08905d5afdedefc654415c6f033c5d5ff4c51f5abd0f4d99bbf38252cb0cc3dad02ad92b73aa12ee87ab94b360a8b3471fa91b2db1b55c20ec30e39bba2d59e3c325f8efbfb7dda71dd6b7d0ec910ca33494103e3a14e52d0c0586bf2acb5dd5bd36987edbe0bdfc39f1fad6a6c81c76e36933b4672c794fbbb114c1efb2b37b9bb572968de86dc77337fadbe5fcad44b1efbdc4cb6fac37ada71856499ae407b84ebfbafdefe3ca1c0fd7d0b8615f02f5f52084d5a52e08231684093d20a34698139a51c592c09312b68b6fa0905da0dcd11fa8d5c216ff5af7e05de106e33af7a2b8468bfad9c52ad5de3154ab2b56bbc44576e96ab8512f96b154651d9da955a3734794c2fc09cd2c4e9efbf42fe3299c1294d1eff1915e8a04d9a38560b79d37f9792727f176bd7f422f7dfa5eba583754aba25ed9d107577f7d6ad66646bd7d4f20415e93f76b3dcf43b2ded1a2f92fb77b68e09da95fe6ed6af4983c1603050e990fad5dfd5baa476cde4fe2e4bbb64152bb3dcdf2951bf48ed1aad52eeefa9456eaae4af282a6b6b96966e46eb90ba596764a2e1e889c59127e5ce0894e5e4e1499bd18b49c2dfcbbf40da3e0f3beddbffbae7ec3ecdc9a35bd6beeac7bebdb787101efd32d57c5a0012640891a26c6197ebd1eab024648b6bdce060be72420364fb23eb841c7ff140ca41cae1813487bca139c494f1b8a276674783475130564cf77b69268e00e6e901deff987e1889e1bfd0480c515432cbc81a90203e3f42931878f6748f4f9126308fd08466d18fa1028f4f33715ea62e308ddf70647a2404ecd5ed1a9fbaa60aac3cfdb72debac78089099ceb07a58339b1c1dee38b21f3589657f157ad42c992b28732755b0cbf2a4809946aee14cc239729a555127ea3d2f67a5871a81329d81b001cfbb93d53cbc4e9a9cb44913a26b90399b42532888bf664c48690a29756260b7e2297c556896f49c3cf64ce996dbc391b5c4834707f0386dd3966a217fe5b09190a7a462856355168bc5ea91fee2587ec7d40ddd61dc6985166de8306ff5e3fe9e25b5cb2fc6a9d42c57f7b0865bb001b631e5951e3a682af498c0a35953064bb2090a10a87e1378f46c79710a4aee6e020a12b9bbb9d31117b29a459d66820a3d3c3ad9058925b9bba517247ac0a9e720cb2329684829238f2caeb91d726479d484073206c372299919aef607591e294143f6b4c8630e335ee55c37a66466b8f791b9e04c972fa01c91058d16a84074793102045c0c214509a8881d436e4289a5dc6f93231c85e8cc099be1c01c39e04cb3642e0f1f13549099b13922b2e5569647342f1965258f396ed72374881182f02655faf10c4110750ccf4204920c5b95d936c036e4abc76b3e2adcae9e14863dcb23295074929427bcbf6d728f3955f258a51fcf59ee23294759ca943cb26cf5871a36a0456cd0830f1091c51037c4a08118928ac0d20128c4063fb0c15fbe945ee0b054243628453186a2102136084215e27cc1110996103dff1cfc994c7481020bf105939212fcd8542122b1a287b70730dfa3b3261f1027097c18c2267a96a334b49137f3035d7a48426c30842a44a0f942f73481e7c37bd37ba1e9e54b5aadebbaee81f2bdddbdde83a7f2f1335b30134ddf3d91991fa09a1f809bf813809be87df7334b9b31475e068491c44513444ba60421022d91c60eb22942f044134162c7c0053a88c08b0f3b24b98926cc50fb80cc0c1680e102b15b4750629995e59111601c419965790445966fb23c32c228c7c8578ed1d0114ff8a0c409b8889ca7258fd40b553c64be8f0099ef0365094eb05e90c6087c64c42c7bfd71a075d09f0381f2048978b6e1fc02a4b259395c3b101ec5b36d6ab31a50b72288c8fd2ab46b12e1a58611b2dcef429df288082eb9ff61c8236ba2963cffd94e3f970e6289c17a5d38d30bae165ab24cb328a5b4335d3951b1d65a93576422f2be7bef3d55607ed162488bd8f7a7052ccb7865a062b4c07ca7d3e90483afd8297097e5511654b24cb3fc280ba32cd3ac98d38e9898fe981a6ee3f3196641749445139497845a4c9b455d5cfd72f7d3b0dddd53506bad5ed8fab65a5bdf5a19ba05eda74d693fed97009d596ab7cb4c0b78eeb762fe8e1a9ad58c8a031ca90d6e682063a3460c86419d3ecfd45dce56eaeddfd3dd1e4ad97a6102753717f4fb6470b37cfc32ad3d4e699d4ae6f7e19f38de32fde397bdbf4e1c1970a43fceb8da7e48a47377189ebfff0b9cfdb6eddd1b4a60bebc47f048b3fbdc3204672959f347f9b4e528c722131cbf2c2718947e99b62ab5d249efa5b5d65a2b8d81d62fc766adb5b6d66aad8502a572f47e2d91e8d5c9fd3ab97fca2769cfaf723ea57f6564a8d55afad126b6d66aabadb6d65aedac564a6df28c7ad05f7b6db2913b5db368179492753fafd22e77a19437d5523a84545a924a26904b31d38b52b7e58994ab945d06fa2397b91f6df67eac997b2a774ecffde879073ff753eec03cf72d7750cf85a3906f041606449d402a6fea7f5ecdb5e3727dca7193ab3fe9043ed4a44a6ece2927731c13983b5d4c3fdbdce4eacf09e4744d3d507bbad24a29adb5562630bd5f4d195d0aa394524ae9370a994b54f8521eb1d00a2e4e991cf05ca2311308ddb944856ce128c4760567add4d65a6ba594527b9262630383f94dfb1514a53e9217a348a04e1aefeea76128e99c58d4dae187efad94d25a6b8d7d9856192a858b59ab93c3fa8ef1bec7928a918fb7fcdde4c5bcacb14659cb3985a3ce074ac9b2ef536b3df627d39ff7ca7ba97d49052689135e50c9348c29619f64c9443a770a1fbe17cb64fa1f56e25e2c232353b10d32f7c33223f060250c193224fd28cb5b2912b0fc8539165e02d35a5399a632cdd1f8fa57eed8afe14884e6a20e6cd9aaf434774c7953db9e76d8f7b8c97192a2460b4caece0bd02eb0adb7721c570573f694c2cc3494f286bea540b9d23a6bdd0277f7d65a2b05b656e2508ed2a62fe4ca75e6e6178eeb5c3b634f7265adb5daf738cbd55abbfe1e9fb6ce0bd44cc36efa14af524a29a594524a29adb5568ef34fcafb1386ca0d7fc050e168ad94566ab1c0d0caf5534a29a5d436fd7c3ed5b1d8ff763a5c58c3f1f3783ac1661979e37f4202cf1f656ebbe80e2bc42653150a31c8fdd2c6b95977f0feb272f5de5f9f0db92530773f107b778bd0130a9736eb8e4fefc7715c288485c34a1849b032a54174945ef27c99f9e5f935eb5e2cc351c8d7ac29307597519271b37c9439a173fdacc0f3c70e92b33b3bf6fab4c30a915eae1041b15c6d0d55283c0d2720bd640a1bc2abb5d65a6be540ba23a69290ab0ed48f397295c9b5be4d9bbefe957247f2f4f8a660fae9d8f7ef3179a4c4b9ef3f31e1f8290c8351279db0f328afd8d30ed3db130266e6f20f8c09bcb400a4599eb9f087bcf18731019e2fb558c0dfed274f0ab85f2d47ad0d85ccda359c8044a259c609c1f5273fceb9702452b305bf5308b075c7ce992a900e65b33a900b29e5422235db4fa1e6cb972fd95afbfd36bc40cdd9e2c9f66433007796471d80c96479d4c444cb1dc5941aa81012e55f26a2f8c2074f2555ea48d82a79eb0edd2557c915e2ac4c9c7e1924249b8b6b0d5ae39568f0fe37400fc79c7ce36dc09f1f9bf756a967e5b0747a74f2ca835f85d5561e3e0feb20ac62e6b6d27e000932a44aa56abd1699b0eab5565ba7d08cd529f4c1cfd4ea21670473f2b94638a5f768621d8455dc5cecb7b593d13aa28e497fe7a4bf83d20dd1d0d0c8e65037f4a4e6a548e335e024f2d6ccf47927d4e93b3deae4de378d682893e77918750abd944b9aaa07cdd77c4df8b37a9a508502124f9fe99abe13e7993e8b0227120d386b3a1c8cacebfe34d473604fffa1ee92d394fcb55a813ff3bfa6260caa51cafd7f64f5e05c9a49465ae887e8af097b488888260adb6848f531a85eb5c14bb18d88723fcd10c681f452a4098dacfec82abcb7365a418a20acc05fe2463c03fe830006a9be718015905fb2043678993d986f06fc8b7489a8d0148153e113d506e22a544d9898810648117cf08d80a2ecbfb4fe4bd42e28b376f9c5b36ef5e3af602348110720c3015e649840802676f05839a1003720a0844e03ce236781c2019e246007172920a00b5201051864e9513f8489a38014d0f0578eb716402b55c0e90454963983d5dd2130c05ba82c9cfe16268f0720482e72ff046d647c641a063e2c6ff7795b1440c39336f84e9e1ada37c91d1ababcb14f796c968f45f958a4a27c96dc99b93377c3a7fa58de505ad3a6053e58a63739a6fad6aeee97b53552bbe49c54a95d30797e6fb9e5f9bdd4af16e753d93f3393e4d1264fb73f1ffb6b9e7ed4bf3e6c782538fa0dcb1bb9428b29dab429cd8c80662d0989d6a812b5e5f9d488d296fc956adacd5f53c8a6e4b15ef29714e79c484379c232bd71377852ee80334fc9f3c1b29c7466ce7e79327f9f76d1ecdfc361b21f4d99b453638e7e537624997e2d9bd5a0b51eaa50a012287b8f3f0fff1c9d1a0c0693c10683890e83c1320542822c607994852877418c38cb2c4bb23cca129425162f58b6e48921e075ae8f4319086c5875bf23b94e58fd29547f481195ad2f9056e4a70ad59fb1b9a4fe1c9a4cb8e44ad43688855355489066d5ff7c737d9f1f3d3c867891fa17e354eaff76b9efc2ebe3de50cef7ea7faeb83e4ee14fc972c5a95a8778abd6fffaacfa3885e54d10d7751754f5e0fe3ef737c4de92deea2e94203769dd9f119e0588dc3c6b364b5076765f0a9d4bb360ace0512acd703cd102491153e4808548946250e9000f20f0a2848c1c66c8ddb00c3262a591d52fa771e198c386a34dd3669a2673bbb7c9a38e51870632273ee5c74496472028ca348f564624b3e2e4f4a3c75bf375bcf5c38d949290e4c987dbdc143acd5bed83e5ad938ff7d67caf03afb7ac4ceef8921502b44d264ebfe76e65fdb26189a0f42b88dafa6d51bb64bf82b0ec480929b9dfd22c9576c9a3222fb9df22b50b26f75b2bed9247342eb9dfd6da75a55df28828c9a7f4130a34fb276f8fee4d40627d53f863fa2e9ca71edd77a1f496db46af9d7c72bc3553de9aa0aa47f70f249a40558ffba637d5bf0f247a169c4bb9820dcb2dcb159c4479828e72a45c4159cb13bc4ef397454a61eb342b7b5ba3e546aa25f55b196a0a7e2b7382034a1c59e9020b2c8ac47e2ae42f201da8f0e206316a10a38725b1df6fbe34fa6d8516fda684472bcbfd46b47e8be42fa029b12199bf80c47e8ec867fe5a6dc0034b6446fe92b4fe4b7491ae91bfa4387465fe92a2df966435b74a347ff110519b89fd1cd2cd93fc2545a55beebeb1dc44b9877273b5256bb34e9a25069eef219637f2472bbb351b583c6933d6d94eabe56e67f2be130a06c3623a6aab61434603b9056be26830c7504a59997fc33b24babb37b0512fec9a89a8eb99cd669355b92344decc20341b8d2693a5601c0c06d3c101a40362b3dd1b1e9b88c8bbbb9b25fdd599bc1e2ff4da853e73a3d9ec6ec163134d9f4ee444cec3964aaaa9a8d3d315d46760bcc0239dd1199dd1199dd1246aa34af4b68a817556c0b4286d61755133d154ac5c317dcc5f3fba75f3974eb7e6df96946e36259acf28915c0aa2b1d111a1d453439d6a40f485887ab169516ad748cfd8d2ae91a291fba90ce8177777f73279fce97ff767a02177b8efa732903bf7fbe917b9730079334579339f03b3bc996fc11e03b0401d1226cefcee378801a44e7170e93c546da0039f54044be47e378235d18aa8eb8108f69447966e2dbd257b3630d8e4f126dc89189ce6b359e33327f0d84a4a4a4aae14f32197b907a0113c5af0d192e44b0ef3d167b1201f74400308d3a7077729290bd1cc673ef319954ec4a22df36efacc43205fa6efd1a74c9ebea171e8261d9337a6b0611d0c0458123cfaece65cdad54433244ae55c9f75371c757288b23f0d87260fc82aea170944fecaf1a6ffe5312cd2acf915c600203cb94413a6c9e495a3bd5494544e8e1cd99a9386f4d59c1972524ee54efdfe54d7138764c704a80e06e3d5876a0e019ac02d00b07252a9534a89540edd406803ead3ac1e9d2504d99a2f803c77f234410e0c183935eae5dcc701b28b9c9d30f5c89b0e73627246808bb46bbc3f13e6536bfdb75d3a4848c5be087aabe57c6a7662c17913cf322b1d8201f9b27566993c052821371724c81b1be6a83972b0008ff2966308bddd6eb7cb5ddb90e79c56b814063c4ea2a2941526886a52ade653c321defa54d705bbcd66f39e2963ded8091b95f4a7cde750104f6c936736e1c41793266ffa81d099147366bb529b524a2985483967bfb8fab2359fcab5c28140fcd542ed993d5d80b832abd55a12109387fa78cb5bf3e7bd184b2db21f3ee43dc85d41bbaeb4abdac903e6541edd9a4f02951e13a787aeb47121106fd130f0d843fedc639c4afdb386baf1f4d2ad4447b3e63d2aaae5b187fa0826a10bcdd220084fda58a72e55d02ea7406c361b9d5b60496c983c138a29c584c1449237cd85d3c8e80c3c6133bc68672cb7b7e67b956627b70205011ea791ed3679b4b4cb68ba3bed89338d664ea764b301a9406c365b14262b9168b41e3a856034182d07c79ef23eb13053b867161bf0386f997e29376219e518e9183537ab9c380dbb81044b162b64fd3ecee41a13673ea533fdea6a331d4eb4a328441c45811d250125fb58c38907a80cd96b4c1e99e7af200a28343449838c5b0a441d0ec000031931fcd0c50c558937db69b5dced4cb2a73b274e6e3db86cd992e728693d4ada926c9af7059e1b7dff93675513ce7cdfecfb568ba042b7e65fc9a265cb893363fc9a88166ad7d845e4c69808b9e3dfdf45f437752c59b922e4d4bba224b9e3947eff873071a85d41a220f7531f79d3ff5e8facbb67f8e4821898178c5f6ecfd1ac96bca9f1d9399b21902fcefe38a7e4fe6ff28830022ac75f5cf8f6634e27a0f243f1922669a9ee96bc9939dd3a48b451d24c3db3c1c293362395f37ba6dbd9d5e03f8e90e58b2079dccbe7c89df938e4ab936477215ff7b91f7b05b9dfbd4c9e962271fadbd3c8fdf3862d43e596b9003bac5d49de92adf9b289dbd56a266fc8d7d3f5481a8d46051e1de90a972d5ab220219dde3badf0ffdd1378f4983369268a1c4af77bcc63a84e76a1c36e98b3824ce1eef6578eb7609a9632ea8cbc8d661bd5da9501c32d048335ac615c6ecd6a28b7dbbcc5cc248c53b956abd5fa67129306a565edcaa1d590c9a66cca9634ab7b82f4e79c4b660b66ccc6106f350b2d72612779ab6d9d74eba45b77f79cd3053905a644ed1aa58dc2eea5302017a4d4bd16671698a8c6a43045a903a091a814cc588b9a142254230200000002d315002028100c0605c3d13c8da264d01d14001078884e584c1a0984b1288a612004419441c610430801c418639021aa9905a51a7fe85a1a777d885192c87d40aff48c6e0fa5f7525ae33f22ad57e2a6f4e02e8d5377430a6840d44c56d7cf023609b4b447e191ab24e6ea31a6943af791aed44f623f94d0293dace141412742f6770dfbe247c1d780bf58ce72a65429792ecc405794987e6fba4a9c47593fdc70067caf59d37e0533100003c0338e9205b4f9637570a3e6b7f6bb97728729e82e340ec52c225559d181e4839cd0c692430b92ceb725d53696b5ecaa2be5e5b1efa37f2ef04433d35d69ce6b3b9f41cee6a803a03af45383da9119d541d4c578cc7eb11193c9aa29a1e03a2f610b1b524ae2d7cdf66c280026f2fca0ab6fb38f2e53170e9c5f6e18ec6de6ca06a716809f4039ade48963d57b8152d6cf34889b3bfa34cd747108b28fac8d9be245b581a68b30faffbe9c242cf5e280251220bcff0c0a25e890dbaed9fcd716d530058f311375a33fee5a9e4f828fb8ae3460241c58cae6db47a9129cd681876c5d8cc570d689ffbcf486274054d8954afa2251745dfd498dced8f872a9597630951bb8cfd65dd29edd95ee6ae562741bde8967671b67b21dcbcce1a40b725533b93b4102f7f5ea144dc7b901d0c7888af9eb6f8fd524e7dee7fe2dc2e12e2d105da75438bf0c77b1cfb4b5d1fe726d01e686f17083b17d79751ec3f05ee7f144fb45f5d97f3568a0fd4abdce800ec8ec17a345cceb544bb3349342098c271d2362c3eea3f92d3c04027079453842eda52963873408ac74c4b76eeda3723c10b8a53c32c222d63b8a9a331966ff3266542e38f664b1c4895fb4f849c826d1a6f882bf8ac16041a4f60e62d2d17b72a20d9822d35f6d4a2381f6153a2f71f386128488047ceb877822428b129248fdff7336f40103206ccc2f01a0a1d33cf243aa070c7a8e2bad50de9257a6e994c52531688b1b6f92d7bdae03444409ed8cfe7a8799c6dfaef975b71884d83b7686339cb52eab4a6ff711a42a65a30b141b7deb90ad98d24e861d839323c568940e7b5e675692f69067c8e5a69af2306f808ee25a4094e2ab5899a787c33256f978ac4024f04c6b262daf4d3b513a8ca80a432b50a5533f1a22ba62e7803d6eba8847dc0ab13b598573a6aa191c184915cc605bf1fad864e4420af4cc9b18f41a057eed4e4bff0dec7bca91e17b7d4ff02bf458c2680a7ec93113cba8399222db0e858b25ae9e622d20fc7a009688cb364e1cc792fad0f3377246ccab6c7303ee26de9623f73c581a36a4f39b25d55641c630f53e1a0a41383768a153ba7d2101186dc668f9418a5a22a62a8160b92be1e53d44c8a080a6930975f1393b91bbb3d8ff2e7a141fe95ebe0a00e1c4d7475898efcd600622321ab0c20dae70c0e1ecb4270b71d3f9f03c8bd1d9c954a0d9ff6f82d7b7be50e874af8a46a95b8b6b95d40ce1aa5307cae6e5c5d4947e83d7c719e8f4de31c1485cc8c07ac0d8c69f67b8bf383b380988b0bb8d203f3021a2c2b479a4042297099abcfb232328902f00476c67740b1d4d11ad894217bfc1e9a079c7ba7ceae95b0c2bb4818928db85405476ad25912b019ee516d802fe7596ce15d296a2674538561f350027626094d0b5df2bcd6a97257eb3c4503f2c19606250f4207cadf90dcdae3a77ea33ca26c467df28cc7dc14c3b1926acfa17581bbc359b79721f4ed8e4e25e3cf6563a41313d250420a9a031ba38a2629430aace99912374a6bb729060742803a0406757670144802535787a46559a4423898c92b45ab29a24c4d64be5b0e637f1c9988f32281949c49f3fc539195c882512e510cd8ab25bf93ceed047fe4fc2eb1ef28a1a7271953925955d4d539481661dc88d38020b80bacc8f5e526b4ff81b279e5b529c946a77f5b3b71d415f7910e6203734d4a2ff4a2188e49c12619c8f5a88736af35c47f33f8fd0c823426ea3278da9268f85d9c54212270a0ca20af9e806887a34c5d16707c5d55fa5281489ab79eb6029f59e1db07f3e867af7cecaf311cea042937d75179b9881fbf2cdbada23ea38530fec219633515c725c341c22e85da09e8953f81ae70f4e2008f98d5ed6779b041829d06b5560cf04692a92cf7e14fa2187fcfc532bd2e396daf3a1e795bca44362ba4e3c228a46f4305caf6f84cb4f2e35477356d8f3732a2dc98e144c4a21724c596a7cfbd694dd168202dd4b261aef7839aa2a93967a8a766ecc2ec25d9ff9c54c1d4738f5cf23c9f832d3dcbc694862c8cee7dfee798edcc3b44be743a8ab4370d05ee6e18e2ea31c0a552f1982e0d9b73f5f717c72c823997db10598d0633682088d64b44d135e1a285e736f4a22f007f6b11df078d06ba34fd4433d823a8c5df47d012930149889a8994c4479901b7b537a479bddbda0bc50e651b43fea1e764798f83d11a6b4d894d917ca7ce14cf62af0261b6e6d24ccc95e859e3199b8e57cc19be3e9da92e6a403ff143eb9e20ea6250a2c06a8accc2df1cdeaa8a9ae7a6ad4ab4e8d51b754578d55cb51d4f146720fc5bd0efdbd181e46cd40e827fa0b6beb3762b7fd80ab85da06530d067f4eeceb976d9448702a81a200e28c307682a672101a96471a08f1b234833a00fae7cfd9708432ca11092422bc2c7c84b50d3d52e970d07fb6240dafe1ac11c26a2e8e7223ef1a0f8842bcdb7fd6b324fb6079dd226f5fef30b614400afc5f9fcf91335dc48b8c7a7f00ce006af901b039a7e107b01fd67a0cb98ff21e8bb1d2849e4a20044d685ff085f31f370f54cc9f414249344441e66ef1516305f2408a5e15102542c44df76a203ffd1d6aeee24712aaa43a5cdf960505d6f31ed52a6591b77251f8117d89c211dab7ac10d6fc76ba2e67a33412fadb79a9c407622723b09ba9189878ae97de91a2fa26ac149fd0aec0bfb8d8796a446481dd6f7a8056b87b23c3d3d97b1983fd07f356848cfeb4dfe59318fd02ba80be24cd2d5701248eb6f3a82a4e9c4a4f3a65e02ddfb3b255c8ffee125e55b3fbce508a95d5fc3abe42fda6bc0772b71a5031b27ade7aafe628c0ce7e393ece5c785626b35c2b4efc692c276684f36330dcf3dc6a25fb8e63122f7c45f53a8ae28cb3b236b5186c2115e194eb30a906913e3d7488d6383acd4bd3c6e025e92123d0bd47a4edef48561d31f20995beb5aa1f7463ed983e9191f7ae5750d4612d7802412f5de84a4a6472027d5912570f26435185b3014670f6d051c144cb5e48398bf089c56e5fb6c9183c5c2a7e67af3f7e97ce1ec66f500471bebbcb8ca10974ab0298955e9dbc0209fab23ab322a9818af32cd718d629f622a3e260d32313e9640b6582dfa66e50a5c6f6622bd690710f568373c6d72b0bc76f7b860034b4af809856be96e5c773b7b4ba7708d5f8d557d784bc85893f6ad1b60a731718a47d3f96c39a493f591af339bb87f3316a340b0d4311be41445f6f33e862e5a980098ff7cfc1750610459854971c460e8f22280ff4957ae740f659afb49dc666869bd6e0fab40e05b540d2150b18ddb627ca5d3390abf4a350cf09915bb9d5d1fef83ea2d2dc3f2ef341143dc430fd611615aa135d3ac688a29145eece13ed36b4001b17dea4a69badcb013a318f4849f375e56ec9c133c8ac6327e20792ebed507cfe12aab9d1719eef2fd05a135456821508a65e218067cbf7cb0102b4ef8745631676cdc19c08699f1be33416fb586b79c9a214225f967803cf33d9708d72d9f95811c96e60158b1e0f4428ca46b9262e77b6a36fc3b49189b4f10d87bd822ebe6eabc17dfa4d5f6096d104b553a2aa4d3a456cd6b762c7977149113e1da06d4969aed25cf439067d6ec64d6fd4e932e44e4db7f3a17fde3f68b6a271285aa3b015baedcc4768dc78dcac760ef429f56f68e8fd46c62b9f796bb6cbf87a62e188b0e833b060b74f96c1d9dc2ec0eb3e0b8a0f093dfe2f51526b94bb1336489a151a76063c77a050cf054b588b88cc25d6fe44ab18f652508e84f112c9f0cb485c9e7374f0040a6029dc528e2ef4f85c52c64dddfc8d8a23bf4293aa146166766f82a1390774c1c1fe99f1955fe0f70d40bf7685159594535e002344b1a7e3beb82bdb9c1b8efe854a337cb149f837288debb53e4c3ea4bab2e76b5dee5dba6a12b475139f11bb7c163868d2340277077ed765925593fc528dae3592f363944857ee68fa1ea0d13a76ead0e65488bed264438ffd258c9a7993d64f2d79c78997832690050c8a00ab8b07e6b8c15301f930ecacda9ecb94fd64243e08484ae054990d51a7297db65933fe79640b2980c66c9e54a6166ca3f49afff92d4e8e178444d89533ccb0ab30eb47728407dfc0097039efcea54087e3e5a98184212f613acc4970110ceacd8810546e21cf004e0dbd519b4e2452b47dafa5b5ae974104e263cfca04cc11fa7acfd015c054190f45a055df99616fe644f8ef13c5f024cce9d82cc218fb5adaf659453ce9892be6ddee96d43fdbf9732414840a1fbee24fe422cb197cef1a466a41f4d6cdf326aa0df81e9daabfa057aa8cb4e4425270d90d36a7f7cd0e36e9d5507f63a4040d5ca471f239218e411316bcc48e1de584e5c4db6189e7106618b50f5b69ada78638b4535e1ae2a14e78424229b0379d2f46e77f23f8e2808bb169c085eaf72e31315a7e0fc2c26b0a182958e49c16ebca6fd44d3b23ef856d6ea91e1e30e5cdbb9285bfbffc4dad89d53e8a15dc56f8ce9875ef7896839a33edac765fddc33aa5fedf380bebaddbf04987b6e0a1c9c37f0d3273d3381a872dd1ce0713d61d8ece664ef00044ab9c0fffe7fa3a6d71ad5053b92d42c83e8c94312a302155430a59d2008f2c1a94c25dc60254a9dda262f52a635363d8c8ed0e74f73c33b600ee005ac022a4b90ac3fea275b6c2701e1920c4b4daa4f3843934db21ede280c6f2d9c8274f3ac5a7abc3f80862a0080315c09948122df49f41c63ba88228db5c6202507b99b222104c097877ced0260e87b8b4ca912889181931eb014359b74aebb0e7a2c5a2d722977b29227c14699cfc5b9512aea18408e78f341ca83e1d88ee779c9fac7153c8a674a5824c263585b27ba960dfaeabdc100468b497176a25344e231e58617a7ab5d894e65275f9bc9c70b4e5beeb8b0e61014e3cc139e737159fa6ebff2bd35bcb83ba07d9262565620903d8de499c0c23ae198a95420c486d93e8d7c09e8f824f9774322ac17a312e16cd51eade733c36585ebdad4d0c96aaab1d7c0b51566cf83abd526aa275438d1a7819fc4925700788ecc9238b5c576b9e558924c69906cb273d793d9de3afda06141f9c502f1ba589fe310a3a10beb7271fc1b3e630b4a093c7c810242177580e6672b354d0eb0955489485977a31788fe53958859206532fd29f1d2e3a1c10c907e86601530ef84aaf4089769a5809809a7f4f527df9899a73b68fe9c62cba007b30346198f7f7d232a6b18cbc1746b2d18352f5d301d120bb0298cb3e6323669f9ba5dddc5c625826c045ac08c64d6a476c69b5f8d05c6b2720cc08c8fd259e81f040cbb603c70331492d8fe4480a35a0da40ced2c040c6c5acff020ba3fd31e30c970662538195af035bf7163e23e3696d43b9e09f6ad9faae370b99e69b9f0c3788b733b8937685560ab455461c24f4c58130726dd5d302a4349de7bb5c429a25a9015ba8b5e49a0809216ebe73f027a66b5c3b4f3ce441a72453db45f38ad61d6fd2d1badb4fd2146b53a8807b1dce213be619a1c7421809919c74d8723b4b38042622a80e57ec3d365b63247a3ee798c2c978645c5e41c67f31edbbf27624774d4289df7618d18198cec22da3863cc45b689159e4cce1f8a445002527f7462da2d3453baac6f1da37d5e36cda3aeafdac7cc37a74c32e1ffaf0cac765768c4564b27c0af6df29d0ac1e1c375c99601847ee4bbd3ba22b9041cbb0f4b93179207908af3f6d0be0b16422f96bbc4e4a1015307524bec2db1da1b935bc3b03c718057c9adca46b1e68b3d76cb80a70bedb10fd51846b103a2e96410fe9c060e45d8ab202647cd71a1294afb3f618509399a99db3336646c2b51eba6ac1c14149b50f1667e7d44b082821ef5a57243015235c6bcc89adc733b1cc5a77da34b4216575edf59438626046825bd67acec89c898eae3763b3d6736cc9dc9ce5b7ce9b934f397de2c9a7cba9bd131ae15327b47ffac615e06f25a881f869f602b10a4495d2d61bd23f53d1fba24eae8080ad4e125884a0606be1eeee215dab2de727713ad7a0e7e4886e595cdb39cd2148e18a4580655304b908f22756240bb4fb1bdbdebe8531cf1d8233167448685456f962854e3a165a0321f0e73fdb166433de4e1c159d37355f743b318a42eda84769ef0d4bc199add74dd2821cc221d0290a40d8c146eac62e7082a464b50d8b85cd92903d026ed08bd1417b6b7966cb3de824b8071f7ae6de560091768e1720a0210faabaa4afb9dd956e6c35f20e123a764936b0d224981dd69fc1198a380f8fa824a7e1f571e052cd724ca784993e5cca1493fc119edc26c70f4dc8ff11e0da2e1b3843809689af4848ca93904a5011803c7aaf4b9b55784ce5767ea2124c95ad8013b33d39efeabf9fd2301ebab776732ba25a71ed053927c9da620b612968db1bb8f47e93738a14c9e59993169b53d686e4364a6e8cef83bda916a7741e83aa62cd3ab517caac605471ff48738a49f6f94b1741637b78e8635aefd47ec8472190bcc7d1bfb55534315b6ce7be88e82a9f157c46fb21fd6b2b5ffe326de769b9ce087860f099e8d82ae4c5613b0ed5b4fe3adfa00239d4ee2452a2dfc9d3b49f3cc9c58963bdf0b4473872b86d071db117ea4eaa51d7748e1807eb2e0dcc104b417d9dadc81853b008ed29743f37e6f714e2b62599f917ab82d0af19b51522921a1627ef52cf1175b285aa3fff2adc9a581e2277be30b9704a7fa330bef5e4016709673efccbf87ed409a0a05e677bbff156c3bcdc382f06c0f2c2458ab27531edc3932ee822c352bf892510917ed5d6d22c254beae15e732c97c05bd65b84d1c5ca96d0e0028eafdf03e24bc4d195478c6165f6a9419ed71880532755edbe2a035c4d862fab8248a6ffef4f0c2c6274d63c131f920d22d112f12937bc224b3daa134996133a8aa8be7202da02263c38c0a9bad0024086905730adb0ec96e93b445346c46c511213969690e35dbb67ca581d5d91e30458fddf1872eeb72c2a7fa999cee0fd55d36d1a99b099415da011a642b5f57e8f364fc203797eda7498644165f7495e915d49e6ff3ce23583ee3bd02259ef1be8fffdb7f9244832a094af9e11780e123ce539f6e254c8e1d020234c54e533171deb53a691145253ee7a0d18f66b4d5b453ac0b54713cddca0f0ff529f192f15ea682af35d87a731cbb0ef70e1ee76bf206626393d7035d1d433c23513b953db9a1511c974d02c33db61300668ee76bd0d36f8695d696e20301aefd4772e3981d4817547c79fd712c2ecfcfd396164deb8352c650d2498a010a8ea08f6c6135882296d9b03975357271504cb18765b49ea117f80130c9baa3e650c375077cb0d12a474660c45c51826cba9311cb99caf5fc2a05680a196a4732faddfd3783c85abdab17210ae0d34c2c1caed82a5ec1a79d49881cf96859b2544383cc1ab4fd466c84a1a13f575ca668afa8dc0846971e41941dc1eb948f90d05c654e8e8dc3fdae7e4dd9bbed284a766f8253dc34719af1f2f90a35aaaad388072efa229d17528399d73d42675ceddf824d69e1a34303e79cfe1f03d2a1859e8f7e9ab77833e2452a0a3a4c93183f4283d4b43d6ba849d3ebf94ce255322ef2982ef73530a4eaf5035c0ba59524b96497a7f8fb0b32e297208580b7db87e038f4aa4b569189660b2e601378913e0226a221559e36bdb2125f60cb0d116bea261a8c447272decfe028111574ec5d64feeb04247d9c394c9582ed4e6f88e3c3bfc17ec7021cde50428f9a01fa0f4c312efd82c3e4a41c120bc0dfb67d083a1c74dacfa01e1e43e10d90a212159bc02c4b6da90a94168aed2788e6ba8fc15ed0209ac870d708056c692a9c01e665faa8b51b582cb40f815dcc918a8cb7c5a405eebd67cda7e17544580d31c574307d20d1bb0b37d4a86b7eb8fd6829f50bca0757f0cf74d9f0d0a0b7b0694452cc4e5cc119fa2e6ecfafb86d01532244da8c295d0643fe760c03f232df7579520900a85285ab9c240333ab77a3cc0cc62bc318b5db17ec798522dcb16f648d73a75872b80f63010c4ee4c569b2a216efec85f44c0bcdca9d441e7823b95163c86bb56ed284636e9e8b9da466904052836758dfc2bda2765b5fdb801c835771fe0912be7bc9ced208d64115a0df0dd5fa93369daafabca66782094f88a5707fff4b1d06d7a57dd63600aa889beb3ce10c1ccc5640fd136d00f83d995ecf7af47751f16d9e158cf7dbbbbca212a9fd09b0193b1eff6a48f99e348a53eb628bcd10abd3490e17123e063065cea31885e089742b98b383101a64ed0c37558153dec32ee1d427b1c2e47007e8bfbe89640cc69c52fc51d6f1c61f118122d0bdef2d12a77d7e0d9b15fa13c24deb7b33f8da05bea7d615ee0fd359a6e65cd8b9971d56c00c910ef7f438fc6d03a2dfaaeaa92bedd92dbeabc300490930aa71503f3b0790b81a9a57f5fcd9753b74ce5d92cd57674a3ed9c709eba603fa3ec1c048df395dfb6860d8f9d98a6887b88a892d25772537edfd42b2bba366b3c97eccd65ab47a23b1bfe542555342b4a87084ea8d330db46b8c24f28dc8693a1dfc37c9b54a0c6d399fb63e2ea6edd74d3474a1f2bb8606b612d8f3b7d35d09c7996a1a9e26260cc3aa5cb0b8f5ff3f50040fa356567d14947cf19cd50a4ca873cb6d1b729d3701759a969480a9bf282cb26a094b395c13d54f3b6cb7b47bda177c6d7bfdba32f8aa0504d4d232a27e70d8261bd3da39eaa9b298d2b1b1a427c9906e879633f15d7530aef8f04b227a5308e04b416cd286053c2ccb735c3001755be6dc33e154597d58850a52eed1564162269adfd1e0b916311305085e9082ea9a428507b046cb4c757f6a94cfa5c17ff45b30420f43c983e15631cf41663b7da30eb443ad542eec294a8b96a8dd8d3722b9954e46a9973d664027ad50aabf2ab1d2629fe09cb5f7b4377844dde0b9c3345517c0909290338f155a04f1ff27560133ac5f4daa3e6b1ba9fbc7e5c8932bdec4014b97d1a31797065fb2dd45908c845556c073f35ef139ed37d1166a25118b2051eab7391bef3f7a605341cb0d4ba6574bf41ef06b4f96d24a32b6e1a5c69755612d7add4c7d3bda06c6a5a04e7695f6b48d6f170c6991317d7c6a84095bad862f059552349eb2276b6313b67ee1e55a9ec06304cc1744f3f8107a8b573e9a4587ecee1ba614a88f4d2b12b7cc360635bdd43e5abacfe55bc9edb794fe916af9bdb7909d6cfcc716d5b119f24bd30376be60fc36950fb147c022b557ccc649791c97a4869de6c8b926703ff70a1f3e70e4c4af229c7a7c6ad4dee04ad894e2d7123ef8f7efcef09ba35d9afce4f1065b545b88cc7684d031d1443be10ae574d32dacd060abdfaa18c17e35f16dc3b8a884aabf196717cd62feee856fd34c685b439caa0b9c5557a02317ece8ad7950dbe778143c6dd1597ada80a6e2ec1125587384d671793f13ae44087414e08e79bff7c8bc218ecca1bc15d724e75f2899263818b5b70e532629e60c86f02ab02f2ca47cc2e46b38d66fcd2f9e5d24aa5d69736efef133c766a3f411e4b296c6b314c716198d94c14bc5c1002a227c7325ab4ecf794cb72c0033bfae3d4162a30dc851c5fa0442eb12b893a8fff6c70e34b773749171bef1e5863bb2538ede18e65b468dd43aaf9507609722fb5823e01871b4a9e2f51bd16d3fcd8647ab7530f46c40341f8b2c9c0ef711ced0e53aa7d516edcf99d4d4e9549152d6801c310d94f6d5cd41111a7e924df3353d00ad63a306c78e0c63c8b567c314324b76723494eba6572a2360212e5a36dadcbe5e9c06eb2f361439dbfc92a42b3c0acaad2ee24326d2a1b38618d9a697cf4dfb8795388d224ec8755250ccdd130ac0b398cfa02ee1313d9184622a0218ae2a0966c696a19041c8cc2630f8506b96829612c5140ac4dbd9c0d134160a29109b211d5554ae678761053f2078f62c82f4383d3847df0e8ee8a8ea8f26baf898ec65014f39297f7957b3e66f294c5b5425e2e2125fcccb6ac6cb4cad6d36498bd5a1708980cea745658a3b9300e43144402bb634544084646e307b950090156cb0f7763188bc139d2d1923008a63eee4c02d2e056afd395886a7377c6360b67234abfd83b8740ff1abc5f1991fa391e6ef45a9ca5c2edca3dd46235b32ac2167b19dec28d3e7dbe88303530acdaf12abee902ecaf47111ec089678ae1f2806990efb9c7c8606e4db8581564cf36494490397b2eb03991e47bbc7b5b440748f39965aadfa41faefa3c44fa58e33174bf3f44688be3bad4aed799f7e69e924b879695758fd127ad932a3fde3996b3c3c2cfab222ad38d2730d9b4b9c254ca5d53d1adf59bcc3243c66f99639ff3ee652cddc560af1407312adbead67c62e0f6ca55b15435c5c7825faa04d053b4efc607d558f4ff10534c5b6416f1f2e73deccd776593620fec82140483cd6f018bf2b2343a88376cf2d3ec577c8d2359248dd9a0072f759b7841022827f7d77f095efbafe460efab7260ae0783b31f78ad8faea01a7701f5b22abb11449dd1dd2ed04c6b4d9264f8377ca01399cafde425ed61a9d146d49281abbc23faa9e22e369e91e80ed6cb39af21c0136e94fcf5e7d60fdc9a4b13230ab241c92940490564a489b4ace0dad7c2abdeff77afcd08c0edeae346bcc7a210297634529deafff3e277c6ada8645fc877ad5d5ee8c4289fae4e6dc140748cd3c6e5c40ade81295f96b3d772be97fc3e14922d40df41728ee790bc21ebdbff287654d0536a0fa3b7b0a85d17c8e081c7b2c2dade2967e9185436b7536afe599ff2519ddaa4e75aea28b87f4a91db876285810970e1848b716d86796e9ea165307ba79c78f53cb59667ab39f375a2a05c35b031ad9213264dfb863c0ffc1af33f671ece620cd63203bedb7cc0735843e1223a668717a94d746fc75d36ce832fded3c45f87c282f2651c7e8a97b283e3d86dcbb1c0333a4dd995f16d04bf7b634c802ee809447592642196554a5eb01ed90f10e676a081febeb48d15df4de58bd73b590dd7596f73c1dc9e8abe6734d080e8bfff847350d42fe1685198ac52e6f7db728d851c206124b4d54e3aa38e1808f8a08fe7972c09dd2568a5fb0623f3f29be19c4b761380e6bdc18e0126972b987cd8ee6726adf96929b0d87e629f29d5008b48d452a0e1b7fcc91313b6f3515fc05026a2f8e80f2ae7b2124e855ab876c6089b5230790754c21fd10c16c4c02ecc3ef2ae5240f3f4c642e3fa93538c613fb4f032b5b31f72c08f6327d7ca947e286d226ec0452c4fd3abc37bf1b9f3b464a117efb810e92977930cca4b393229323c8359313ad9520d7e4b42772a711b1735c4fbb9d2a53b023f332af7e6bcb052246dd996243c72522a5dcecad90a5c7ceb8fd7b7b862ecd3754e7489911e5d060ed1c1041b9090054a1bec826d9c237a53b09d674c5d0df1cc20c149a5d13e557a18b4d6b34ff1262a5a003e462b96c3470d0a6b7a9d8e2526741025275c08a2dff7931b41854f7abc7d62c54b55b80b8c505fb726b07cbf98fc6fa4e5aeebcb13f2bfcde1b862211ed9f890cf5235f56c07732d6764eb97f9031cb6239da9973781c8b2ceebdd701532a56aebcc3c24732444c0852181b7a80730ca87634ea43624e8db9c5ce8578a70b3031ecf8a12b461af74a3e14730986c9cf466b0e26ca2ef26fcd1d77cd04d5188131b34f001a10b5414098d6f5da503173e45b88d9a88760fc4c5f3bc8a44806c0387288524df0c32436060c3253124082cb42438c938819dfe243ed8269adb6b09802e5b679abd74d97c1fa93a6d097ad3bcb194692382488407179710eaf35e4c4435e615537e278656362509ede911ead16d8eec03cc0f8989ddef37919f0b997d7fb867615f12cc4e4c0838d54be9c9ffc527a330ea2526097d64c05fa322d71b8e15b76270f1b0b78a69fc8746a4a88a7c53f1cfa83e4105fe2db6e821ea2d3971e6d597086b32d5f56b8c26453b3a6f6d128618bad72a74e1295538c2f7f822d639d56c62abc2c6078635c14b24ed4c6434709857fc8b1bed722c28136712110aa2e0a2aa58e8c116a9ce9c2e565b408ba77b1648ec5d3127f0a01d6a3bdab5f1a0dd8396398760da8a45f33944c5671f2a4718a458feecdaaa35d358614c7c2ca4f0e50ecff10612b745de14ddacf810fddb20adb21db72f585b4e5ff3aafa33e36cc19bcbe08f865ae38d0d496fe58d8f26ab06f5a4facc87baafa00651f036e0dcf5114264f6ff03ba8f68378afff430c9586700683f4f44a239abeabec99b375feca9e41e97259acf66a6152a8e80eeffe7459b735fa67326f2bb733a5b346e139f60706fc942acc73ae063aec97ab4f2218311b035281bba0c46bedcaa8366d4fe0f8076d70e3c311fbf0e290151a3363b12624716cdd24ae8e69686185af807addfa15b016f107cb99b47497a530383081359e6bba05c87f475d1b23d3b582470097a311dbb6f9d9a2cb8d989460da2cda5d4b297115ea0d4e0054dab57da439fe0ea7e2684a6e66a5101ae9e12fd7db2bd733b7b19310387ca3033aa3ad911491f5d91a7c503dcbc5dab32188cec044b5b08f845232c1434bd394b3d9dbc6e11ad8549a904ac9b0a70ac063a883752a52c50900f33dbf95788c51c8505a6ec5a225976c61e90347c01689ac40deaa14c774868c2a4130daec226738fafa9739af3b8180e0e4aef67bc70183928e4b99d6043f8ca48b671f4f7a8f0425922368534a2127415e1d1466e6b2dee65cbd8b564f8159c78210ed983773130f396faba2c484fac3ba78b9fd3fa1b07978bef097a7d1e96dc09cfbb22d65b466bce93dff07599f8b527bf4afc64de5b4192561a7e51bb9e2a23ea78d703a3f1d795062e5e19b823c32bca965f91c4046d4fccc6f23f665fa3247078682630d410c9dacf7764cff0ff53534f8c75c6db52444df196932a73b77298a2b4d00a9dde5b43c142e3dde0ca9f1f38d0f2d9b368c86566d9ccafb4001dc179e4a87c34539b368b53ef91e7241d35b209218d6702e77a4f98cc4e42cad26e49dc89978ea2b6099b2aecfdd5f5028f9029ce66530d08fbbb19e6a975eca1cd6e34ab64a17c94aaa87b3d0d029d10065f0080fedfb86092857382dec205439a0907d3a7293cd1d649420a94a33c84c7540a649c9db3ea014ac2625538048a25bc59b4f358802a884ed2db3ca4741d85cc2780cfe189c88fb63355b755bfbd9ec127a7aa6976184dc440788be3281112421aa4777e516bd9ce4621f8d236feba3c0522efbc57ab0da82c0939d184bf08c4b25533f80d61935827794573dc687f6cdf9fd036e54be78ccaabeb262dd407e41435207828264489103fac7c2d4b35af1adaa1f09af9c601a08b0ca586eace8615aa7d103e3d0ad83e903615e8f6a118ca78dde8bcd4b39912a73432c5bc34e84290d4e4012279b43b65cb1655e2666f1110aa19f9a73156e18060c818048caf0262ac41f8ae5e38833b7c82425612ed50d1bed0b276c7397d07844079095feffb2e00c1e37c2d110b0a7b486bd88a990d64b079d2813ed05e049a0989b225241130270278abb4fc0e17c527e2670ce25c24e01bf4f408c532101ebd9a095a8c4e5173285a45cc6fce0105dc1bda4d93068268f899ba82d627b3fb714c584a1bb4aa214a721fa6a996495aa7e116bc2356537ae8f82a49081d0e9fd72c403136090d635efa1722c74e186aa71724cda9a8288f26fcb69209bd660d4e46fbda283abed223efabd1502064ea936b855255984e8ed4270d2b3a674eda3e2216c748f4cb11f7a68b682ef09e11d8a9a1fc7dfd6f9f634006d4a85f17799130f2d491b98d8e2ad601343e9e647e6038957d3ced3dfae744cf643a140f57104618e681355917d2610497b60734f98621f8f751c8c2ef063a36d56a587ffaac9e89070aa63637761791c2e0d28725c038caef0823e16eb8a2497fa442c546cd9af35226008900df195605fac20caee2dbc69cbb173720689b80ed988fb41ca8935961121091e4faa45140eebb59ef9388c603938257b168a01aeb4fbe4a21a6bfcf158bb846021056fd00e3c222537c9117f310f57392c1e3dd4a14c14233e0a2c06f29eb1bf27d6631ccc22d7e8596ed0c26b1fd417f2872cf8c9812af82100de752f3f890bb4d8cbd831f32d7f65b09439efc5e2f540d7f2da10ac036499da69836c1eca97e3227285e1b05c9a15b07c6e8cbd0fac8d2f9f1bbbb358f8182aa33a9652e730af68c484205102efa062eb9835d6ae5bb130c8b1c134ffdb1099ff8834e9dd1b0fedd4a0c0e289a79617e6de14a7aeb8dfe8e9a6a822ce44cbaa37d71835faba54a92731d8c2d0d518b73e728fa73a9fb8c140f0e344329b0f478f317ac9f8453c09baff7bf42b02805a2b7ab8fe53365dd0af01e7568ae20e59851e147a6e4a864b8a8badc14a59cace2478ee140de1253f0c0f4df16cf1c08e9925b59b56021164be41cfb01413d6afa4060659ba997441909cbff186c9556165ddd82155168c8ba819e55dfaf380f15be214105ad52c67a908719f3861336a3cc3caefbcc854e24901ee7b4faa3540faedc2c86ca4151f87146ab498be96853798c57f27c66942386f295dced6cbd8ea485dd2729e6641e6d2f4b2934282ba5de47f529748b92e416390ced59c89eb6987e16a2801654f353b1ee82ad7632ddeb5e494fece8303945c5f0d9b5ee12aacdd0c7a048be0800b4ac98e523763e20f282274808229ab72ad8fcb4f40c8af6ead173885b6817b42703fd7448f680221ff6b126a1515f01203e650a16904229263ec059e09affbe4b290a66a98d37588ff7410455b634f7322b16eb036e2605c242519ce160ff22c2cf53206bd363ce6eb415c35f2b1959d205f54e5c23685d1d5607520709ed243a3728a7a31166d1fa01a98af13ccb30c3a0e632fbf99d1966de16a30fd02081490cf5e6fdba7553af8a505595abd8d3397fa922dc9fef76d1ecc18565ab5e21aa98dda63852a82b2a7244ee83b58f524544dc342fe09433c8c692b17fe2d55381f3e93bba37068d20925150a4deed14fad37152333749037174c33f66eb04e1be68342640dc062b2b8adefde467771a3a734391376c1e3cd7fcce4de5c6cdd967d2e78826deadbdcd89c39e75a0ab774c690fa1d295ad34fcd9c878a2da978c842e5c2a0a21b6ced3ffc162485a316a6cc4bff3020f205ba9edff3b107bbcda76ece2af7059d6b2dff5cb85d4aacc8c0a5306c95e508337666577a728b776222886b7093345485dc95254992af5fbd761422f5c4ea6f8ecffb6ec3fb6d5cd46cf1a37844482462e3ca644e682a2659d4401a76378b9937f51d5a2e90b0b4b06e23247fe2c194c628eac0927fd56afc221135808c6d7335c9c88324fb8d6f4900d0fc6454bd2de832239d8383786f41bd0e007d5448338daa17de4f616740dcf3cf9b6a24a01df4aecb23036a48ce99504e1302416b66c57c186f997678a2bad773b9dafb403bac5f07aab89ce15fe902bbaa459958c90c49f7fe40cc2484474ee33e479274648708723fb3a241b14f74ed65ee6e88da40d7b7f0cacfe6f58acddcc0de3634e46bd0933747ef050828d7e7243b9ad19334a0e6bb3520a0de3c3595064d06496e18222fd36b5c467ed20667a3f38774353af74dd60312f1f67af767b2fe3c0cfa5297a91f56a5053144ad67465a3cb226086cb7e895fb10cc1e6155efc464bce84772853a0a5c5cdfbe049059a8da964cc24a79c38cf48ab3c421d460e2e51731c8440df8af3a918a2bad977a7a300f39b19d370488c16ae217763864344e510a08ef9a2c25b7199f150b8e19a0a107d832cc1c1528074e3cf1726c34f07e14c6f83f4f82c7463a11a750b761ef2bee6f0ee52c8bdbb1860824258a3c7c637ac7596f09c6640797eab547f2217c7dea2968941d58bccecf6223c98f0b36d82d67e692a79b05e6d64f12a71b4c7d1a684558a27c9c9d40b8cbf094119f7464c7fc8b69e733139c4078bca119c2e9eed332d6eb46de8a145a4731b4a407bc2bbfe18425fdf8c21fa9d217e596d5e5010066b61899c618f23e9779e05fe19ecab7fd2c73d4110d611539ba248e8aef9de301bbe0caf6557f27ea30fde8855fc7ab8ed13526a6de140d5cb597f3d5776922bd5132c4256489eb7709eebbb1412b52afe789a4989f81bd8699130f4b0fc3db6913a700132c513b5c11449a644bb450e124912989bbf78047a103f4a82ea6899f48641602c17cae7bbb8192a64a34544e8da0418a291b9f1510f60167d8f9042018e397f54b7c898114cebdac6f269537c3f1e9241bbb98a09a4ffffd3ca88b54b835c8fb1ba6c9e3ff41056e562a9e814954c8a1237f2082288858f34d24a0b530c771914a3a61cf141af8cbb11f0c23c1f7f25f68644c0850ee158053e313e190820be961345b0dfbcccc0d1a7ed36ed9c6fa20b16d26df787e52406458ba108e6f2369d6d368dc61ace4ce495d9531eacdfc023e1d1cc2e8f0485d4da30d987452f81c2c206f619db78587c0dba1f1dec3c9b18f051633cfbd6507d93f8145431a0c8222a4826501f01986eea95bb1f93a75cf21199b8a0900723234394336dec341c66c2877ec6f43da9363a90ce0e798967ef2c90d75ba5c6b7557021e9d50937584a899d94368307a39909d7547e3218a36b9a405de97293efee288cbd0a0db1173038e448b08a8d19b9184bdfada2d8b1b2145254c30646bdbcf9f4a10946f433dd0e6f0054926f45a32b866439e12cc56bdd1a1bdf88cd41c7c34703528471b444808d694930bfa8cf5b2f46d6d0987c21c7df5b3a0ece3f4356aa29a89a2e192ee40cb50cee619fcd471911c89fa44972404d6b8867bc981934849e18c6e519786f680065de2126c083bba31dab32c763b94da4c05edae6a959d0e56d915207f6a3673ddfee84677658c7db2c8fdbbaec0353fd08d58347e900ee350e2d78532b5f0d0a86a8a14b788404a6244d011f3d5398f1f5e51977db66c6b04290e0870192ab2de02673d4ca7782f726e5302e783e87e0a182a408cf5d9f5d75e558d66c09c050978a17387e7208b010eef465ef72204861e8b4552fc9409354af0a630c5b9fc2534a588e9518bd95c138dfd69d43fc7c8b062c5eef4a0746aa1d3edb35d259654dfede1897437727eed45bc5f176d2ac89e5705eddbf89c731b05b756230a7ed4e5c1acd40cc595a1d345beaabd3b71675715721230f522a095ef9fd825c4375999277485eda6bd4facb9c4cf43caa4c35cd0754ff1721e545a70b54071e1c5acb0fffe58dbe14e2f393062e2f0b5369b26f65957628263977b78109e87fc304777e24bba8c435b4806f761b68ae471c8fd02caf6ea6b0c406c71ecb43f655bb8eccf0cb01b661dbe83c852410cfdbeacd137e1a269a8ac1439844e0306b5130780aacc1feff9a5e4f3ac110abaa28717e77377088c2b6458a2245a48b7b22bdc238a0bef478373e74ff786b820a456693c686ae26314547e3c250634be32fb8f2a941e070759b622843bc9d718ac29be2a5a7dac3a595d2acf5269dd27de1e87411d5e8dc451888a1668201d02d6fa7651d19759b7cebfd3dab6550212d03d5f1904532f751a63a9aca271f2258ff4396cc7b58d49bb7bef5ed77a1a093fc829515b21d8d947a969441558d01d0c13910d528570d96c6ddd6cc9b6d70638d37ef45e12f7de84e4fb02964b33f47b7145a6962135ac0c9d42bbb5fab0861051fdad00b9680d32f2fd1a1f046126f303909c9eb3af9166f200ff1efb148d3a95737531d46bc19079feae372fd5b2b47ddc1e14c405cbe74063a903ebc0c7e9822b983dae1a25f90fb8f345c94f7c220cbc2f9a5d3d4aa2978d3a87e5507aec85a43e9f24d4f42589e8eb51ce6fe5412f5a37749cbc1f57559086031dc8f86f1f21a6ee0f8fa9b40a9809ae1cda39fbe3ffe28782ef1fadf97f5b975fa05a0d31ab7708bcf58a772c06d324271817f6facedf3c5ba699c588c3a4746ceeb9b786edda946583e6459765c6d09f66b1f5eb12aaf42c069f4d113c15e630aab4c1cad43ce91b5ac26b465a26a0c248b40a5d31b9e4a327c2c515a47e7ca49f96f54a5494302559f96744c1665d5f90f8838e299b9e0fc4b66547aae701c9c5fc6074d2f2dbfda957ce8491503114b6c4f1da457218f2687aa51ea5913cf25a78e3045a7d7115c15d43da6f3b14c96076e838d3cc09acf23f182a8c050456f9b75cb1b835f8082bda43db89b4b57fc6c402743ad385c42bca46294234c75d98388aacf001ec40efd915ca1c4ecca197907bdc32b4a73e5f9c8e3410fc9616801cfa2b17a5fc409f4ee02956a4f4f0696099a13b7ecbae437efc5883767e8e10b590416b63088e5e9dfc4bda4869cd01fc42686602267d7cf59464458e9ab71b4d5f5e50eb8e002f9cf5dab98bf6a35972b6b5daa4fc24228acbe98a9fac70c8f68b85412471d4dc5ac728c28d23c4cc80bea7293f0b8485d0d0ff2ef0b1e9c93d1f11ac03b53cb16df76514d34294dbebba3a7c97ec84a20feb7cbb96c49fbe202a5fc6e9fae48ffa640ed90eb2b4b93a59d5a0dced588c43e019383601010432160e507a57935da803b983e3b1aafbb94e315c2b580e53d8a1c352814d8f2c782b83edcca68304de3434ef72317fbd630cd84958940a360d1c49ea991a7692347ba3519b7934a2f0935e63f7c4fc885c4a30c061ef516d9f0303d50b388f9548b549c3555719104d7590427a001406bdedec3df4d363d9a103fd058727478d352fc79b38fab8e586a8f7f6159fe852b706a1d0b443eb9e82123b40ed7685ff533b735fb1790ce709c6b7931e4a8c2156bc11579f072d05b02a2a66dfd86c7da48c7327a8d72ff8a3db08ce05d8c5a6f0ef0bed7f5a81582807ef0499397f71775289889d453b794101d0372627ea380b38b9c362608a6fdb7d6945705d50bddff5642e67a3e6839d110faf73b85ae2bd30d79175d68742070e36a3b5d336aabee196c423bdf9426a51d68f9e5f756c9c7c2c9e2824210be714ec7c43c0c73ce3176fbfccaa0cb67b20e4c05bdf2f0c2b83746d6a9092aeb2a11b236788006b1f72acd0f2d9f05876571359970ae78d1a52d1b4e1fef8f6dc01e8e9da3e89d9f7ade5bdffa5303fe89e5378dc2607b0100b26c53a38d5d016a01ba4b4e5615c37c3fab51dede4d6cfbda4a12d05cda8785cda7ca07939958e16f951482e498e15e8181c5147c80d284c302db90aa405e38ee530bb66fb919fbac9156766e905225f7264ff602ea5169366b01bd4a848d73d7eb4b7db4379f282977b43c4049b74dc94837a5b96b47b463966d5263faa32c5682811f5c422509ffe69b1e75fc5248d7b57ce53fe9854641542d7eafa8e5b1a9a029ed035a344a9d529ea256e57599d2743686a279644c69e58b3325886bef3e7ad56b0c0c05e48e7b15029b31c1cd270a26da1cdde07da89134ce24664cb081f252d40d7b81626fbab9c39ed7422141c6006df0697319634678dc347d96115b3a94cad0da82a10be433a6b034586072e96c7a8fc24def7292852a2171f414dfd47719ddd63e5f6f8339078e04f879c50620f42a0b1671abf62814b4e1ef5b3cb33294a8f370046e767dd07e70145295e3ad70728ebb4435b3ffe8004b54492d92b1124cfe418919a6dc0d81fb486477b2202d82754875b7743294c5fbf9b489797cabdbf57a950a39f7c7ed7de4b343b325f38f2d5a3334e1f5839788a05340a286d36ea4ca2ca00baea32405ac1953b7e1ca058e29c541d609d8c980294d89afeab8da5e12039e72b0d179fc231d5508d1e9fadf6f90b918fc471d0842e9cdd74bd80a09002de667950402eb3e227c26884337242d708bb798620ed604b4e31618a7db50cc82c3cc1a4222230b6b7c0c70caa8a08337239018051367747f7aeadac15a7fab89f87c0c58ab8993ab3f7e206f6de590bd6361519c6b0cb6ec2ba0e74d829128ddf92b8a2f5aa881b90325fabc7ab97f7e98408953d01062b6170249addf4303c5706b3bfb95db84048913472f5b2809bd593a1e9561214d93713bf4aa6dc3f20c89ce7ed87fd3a25ae423054fe4d49c5b4cab35730bd5e12ade6fccdcbd78a0c01399e6f066ee2b0c50e63c9cf08fded7e8172d38bea9c9b046c65967637893f43fb4cfd9b5d0522fc8b8a47c6de5c7ed1ff0e36e6dbe3aff0bb4fbe7261d4c47ec439ef53cb0e740759e5417cc43b4f323f2bf1cd4794438df5b2f9f2feaaafbe84567aaac06ea2905cc359a046bc6a7b22f79f941420415c500f97e0f684dcabba3b7410c81a3782c19dd4e61b4b899c7df868305f8e2bab9243e39a914a7c200c696b5f8283d5ff6f6c68072062c0572bc89df2f06dee3ec273fa83faef4c36fee39183d2087e107e80cdfc5b3ab5b65d4f523f241e5dcfa09a17224fe1cabe732c26233b129dab42a38283d0ffb95515e5af21067aa9447fc64644832d21780c6f0c8a7b72b49feb81a1b1e0937d16e194d1c15e6d666e47ac9356ac7fec6e9d801ab1c7b7ecf538d6f3bc0a67725f17f3ee1ce2c5d6b0ac88bf2c2e25a78a3a9b0727c7f6d5c435170dc669070938442c6f19e23669b5c5d4a35b2c6d854bd63484d5b7b32af988af0dacac2c5be7481ad066d2edce60025b0bb3b7e34fe64f9f5e2cef1b396775ba3d8791978dd156ee636338670cd4da1a4d6b896673967994c10ec495a95f8b5779478cc1ac4f7ebaa858b8ce0cddf7f2fc49890e48027c26cac72d534f11e48420ee75744ed61116a1701de447979bdab11bd58688e289d6fef1e853916b0f84d87751a112daa69a7a05d7fa33de0c8085d10e232c0290737481f71a9b44f3403cb0780699d834b67bd7b528503cd7f8181cb1b3419b5568692111ff817751fcc3b70392770e92357c96ae9320af172d32b22b409b85c0d804b6ba391cdede4df5233f0c205124aeb9b2850c1d8d4bb58cf9bcbb45c76cf098c457ae8370c92e6b9b6300cb9b58447b5797c05b8d311601b2955a7916db08bfc918830ee17c81267d1b695989e88a4ef8b9b5e8d5f958c16f88ae564179d24ce6cef6d22135f436c3707156f8e74e01c98e31c89816cf815f874dfbdc2b391a08c8997a45de01c6b0d3b569ace8e981e6edcb41955c74dcef6b15b394b1ed3c1f1cf7c93db2137f89292721680b28ebdcad8f1db9edff163af79d21a2831ec7abdc1b5c86813c372f74cd43c4b53074dc87f93ab900b27073b40820e3721ee687a74f74a1e5fa2527d2bdc2266cd6ffd0fb96e5cc887433817790a8b129a5f12ef1674b30c24be9ce65c227a80a025b52c00596ba90e782f29f93fac7196856d93fe776475e9fedf3d01e7fc90ac308d7ae098a1e80a85776c35c41926ea36a0a87903d8e21ad1f2306ac448e083d04d157cbe309adb2f0dbbd9e211f1ebe8a0087ec149a01a5fd779bceb90446c4121ec47ecaf5bc29ff8d10bfa3f55028004a684f33c47f83910301f5e48be41bdf0001d3c49297b25170bee87ea655fe822803cdede813c39f64e48aababdbf05b40d45e1ba4635b7d8c485a0e71f8017eab89e85206a6d2b0809a737c455a3bd92ac7a60122d002a1af08017a7eb6780a84951677f2aededbe9557012f56fe0dc6c9b8b4b8c10204bc18cbcccae62c6b8135c08bb3a0fd1f6d80e24077425ecd49a59284454536a2fa92efde940ae0c527a70f098017d2b52fec8d8a38df290fc1f57781a693e6abc982f5dbdd64ae9c12eb9a482cce2db906c8bd8a3736f45649cac797a9e1e2868a166972d8f0da868b354375df79b17f8372f3a09373b684861221b8f8e8bdd0639b50658b46119d7aafece447c84c97ae9d5b0f2f24d57e09c4a459614ad3f91d80326136127025c9b7080f47b858bc181a28a29c82b2ba01d39f73f74a86e47790ffb00f66fafe8a8b0faa005d28208ce4874bb4b101d30836dc69122f26c25531aa45b4c548638ed840dd5d936816a0862aa5cf413093706128057e30c8e7c2cede000a3fae50e3823a34b54485eb24293fea90420ad9c92784717282799ea46381ad8e84810450c34d6101497fa0dac3d7b72329ee32d9c056e10a122a180ac6db916c1960ff2105c273e3a3082b1066c2c50986f21edd5b5ba58b517f214d853ca49c02b2f1cc096fa83a48c8fb9cfcf07c2eee473f79267c2f70cafe81835f2aecc406227e2fbe4089df25f94c5f4d6921cfe34f71f47c0a31a69b5292fde877b5f85e13e8119adfe8e7713087af5d404c86a64576bde95fb53b96bd185630580c08c79efa8bc7522006f463a70c6a22e16af49c88d78b9d4a1dd449d531e233d2a5b91942ed231b62efb0162ad5aa021ea47cb1314bddff2d4331c2ce1ead174137cae72ab86aae8ee1262302fe5a48345d0bd5fa5e8b17a7be72ba204b771af5893e71409054bc81091dc8d9f3e7fd20a1f54f8920639155c6c691caee0f6bdd3b4d19ceaadee0fd79d339cef7887bec06af6927441200bf6edeeb92c359240794346a37338a31a7a505a166f2f13cf95506125233ef0ef8767e6f017a1c99ec95c95f158f85bdf2073da19c3a4f69377c722bab65f780dbd5aad7601dcf8c7c6316d0177e61a9999078414c8828c6e9516e5026a802b6a222f6958173073f0f489aeabb8a44d77bc047f85a33e1335eb99736c27fe85db100ad5b385ba658c0975eaf3c07c8d3a7856625e861978f715bfbd811356886860966cf907a8c040197242bddfffb0b718d8ed53c658f2f4a23b94b1ad97a4c35d3a7255dfb23345f5dfbfaff2ad315af19c3866059ae792a5249e59c1ce2e54ec0e264a9017f9f46caeec17a433baa928fe83e9e5e993032f5648fb4f172f07169d14b36accee1aa6b81318cf7b45a8bdc1f6acb325a0631bd1d9aa036956ae1ec8f8c46a4088d6a9f6da28bbe256c4bc1ab47de2d7e8409636f4b6070e53aa061deb8b64ff21015f005189028258523ddd0a0617004bfbcd5367a5253fcdfb3311ccac2b8cbbbe6c94a3b97df3758d6a87f5ca547eeb0a512af80646510a60ba4d1ce8bf54be942c4d41186687e80a0b328d405940357dc59abf0a5d637e5900dc34422e8267d9a0c48ce72ad0b540966b8bdf094cc34d8acd1e6d5ec773408b7c3a9ef2ab05b4f5585bcbaf975587700b40e92fbd29f4577ed453d713aa8257b6dccd9ff47a6c416624bd2f1b906b1c0b922e85a7e7cde69f59c3bca19b14e60778eb2da11aeb9d513542ab0e7124a440a9d1c55cea1016837bb28cc03d831131ae6425ac93785a5630fa5035451e7f532c6ed62ed423d28da085c14d02735f94dd08e41fdeb9da4d4c1a876ab8fc9e03c6bfc591994ec4a1caeba46b08a326a8ebae3c766a4829535c26306e337a2e4bf040de3f8b250a75f74d2c6e9892fc2b7264e8371a1a1b52c7192c8fc583bd8ab1b785ab44284c3602447509a98d8148a25397090a93c21800e5f94939cde72e2a626db38366b3372741ac45d6b1f9b84c4e121d0428293c7b41e3e98d418daea28c14964ca6c80bf951de53ac1c4ea44b385bcab73c60d4045bde93a458c90223628dec8fd5856bc50619b00a93bfab21e47ef79aac2a3588efdc7d6b1447bc0d3d6ef9f914f2a20ff9174a0090c8f00b4ab4b96c6ea6004861ea827a78eb1a8e2ff848ceb76080a0a3468304858f63c85ab589fa6cb72f116e7aa2f27c3cd1d6deea5c4f8657b9238452edaaf5352bdfb5a917e76de3901fc1efdb34d04eae84df40752e490d0e20481274fbea4e44f1c9a35a99fa72811c3a9a58b4be0fcef94b14b149168d44c6b30d45894b4cfb58cb20ccda849b27fd9b4c63d47362ee07c2addc3633297f09e58daae9c983702db6af4acb91a49b2074a40b77cfe7c435cfd7cb92949bc60609db0bad79757848fb66b59b39dfae4e1ce4f4899f2cc53e183594a2cbd230d5a650fc372060795ffd4842fa40eddf44cd6c9e6607d146aca46d9b617e8cb1c6b7df4ec80c96a38d119c0ca36070f818fc0996a79346b6b0a0b2128467cc6bb1b887aaaf58f952ae6d74d11952beeea973b04d1030f8397a111e3e24863fcee9d276e1b606aa89f2431730f4f2bddddceb3461d42fb0d9b2e777e9d690cfa60fc2d1dbfc46e73dd72f72a7bafb7dde9e5167d428faaec9d60c1474dc39e57b3b0a39db7a536c47a4fb2b277a7c1fd49bc81628d8271b271e1265817796d732d6ed51dac85c6b4ad203e2aa821e0b27b9f0a6ad74c2300655926fa46a60165ece114ee0ac71198d1462a463cfe78906629b729e3f7e2db165cda25899b904b890e2b95baf9c69c5a5bea46cc9574642556760338a314e9ecee7a1ca19b2693625e8ab142ececaa1b5570c82fb4ca3ab96fbd7947bdb36c3ea6107fa0341a4f2f30212875099b990a2eb2d0c6ef7e31c6d4b00ecb5651c6cfa16ab272c42e9491915d540682a180b68d90cd719f55182409783688bac2770615ffe7d03ed21b02382fc964a3fc25c74cfc82417764730ab8dd06eefbfb2584e41274070124d6e2f6e16a09f3386bbbb8d5d848738e8fd24e65c3d4b8286f1b7228f3386b87399e38ca3edc51cbb87ac285cbe71d7ab3ee30b7f8f82c25eeee05c75d30c1265df8d670934a92cb8eba002b4ff0c4cfb805db93b98f8e2c7de24bf38e1716772d8ac35d578085b80afec9f89413cff44b2d8e18e6a69160aa1137c0812529ebdfa9ccf03fc0e5446e8648f67054ea9ce89d3d3708507de0d48724ec22bcec2d6ffb74fc9ab3fe11839a5981e5a080f3a4e7a323f83b4dd8c6660ebe837bf1f7946f363456ee61e3c01eb642bc70a35a2108d7bd271a60687b75b5a0f9f458f77b1b88a6ed6812e5e260f7a08865f636ad9914fbc923df09154cb2f44884b6265f4a7b4ca5c57259f505948fbe5d9a43290a17c097389ee6060edc6f2006e0b3493951e5cf0e45c94a72af67a7fcc7e229fbdb9b9e239029fa9da2a0bffd349a76d1794221742a7c1919a769d21458ad6e147d5d2d9a668a62657c8336783d7bffce1adeab9e1cec977a748b4971be849c6da7bd64234bd56867a4c41b9d0bf23e41dfceb5ea4325c70c68f51901e22eadcfae7add1f6a5e46b5a517c5b25403c7ee6fb465137280a2baca43ffe32467a11492d24bf40252512edf07984c9f9ffa117cbc123a56e140aa1206648ddfcca7286902d5fae1199aebfc12dbdb58a7b74661fd005a50240059e94ab764072908835c1e4e242d7d135753a40295176ea023efaa210cbab5ca3bced5faf0bc6a28500c225baf4e20f8b0a73840dc7f9b1bf9c9b0baef2d2b712225a327a4089b04d5a58aa894c321af599138d2fb292573a399263c3b24c2313ae04998152b9fb64d8793d2054a0ca4967842bf5be3e3259adc281b183dcf12590c785882b68cac8b503a71ea285e6ffb19251c42c0dc5930d192b64567577860a9d8881ad56c3e2a267c14d81c53bb0fe63d923b0b6371b1ba6126d28ab872419299a586c04682bb49c3b4a1a2acbf831bf83e2bee0483920de48cc5eb7502472920be08dd1f6633902409bd545d7d3509a78c3876ad51b39bb52bc2fb5cfb420d89a3d997fcea3f983e856ec98bf1dd1b1f8488beb85a134e8262e60859c37230b3fce8337a880e55fe3e43b0624bfd23a0f00fa7c0ee48918e15682983a25e0bb760ae7369e835d48b1e1d725d3675b3b8345e7d852953a5347826b6d16364bcd4a2f22c18e6637d062d257fd6bcdf4c172ea1319f41288c201f0f402b68e4906b683a7e7cc5dc98b1c48d3f950d65dff2b8d42fcff1f1fbb76a62b21322e3adbf6ce2c85e695afb9fad43fa8b498d5a5f96dc62a7ac8263d063e2ebe236058f762aae42d12444817025a267bcf933108445fe6dcf3e66b0fd6079e3bdd628e33ee9742cf176ae3ca85b8f1b6cb85874e2c39adc6cb7cf322850156bf2fcb445cce711a76e4b04b2107456873aa66a5e27829a0e58539bd4fbc433b2f4a3be603739e872a9cd9add805b748b925fb19cef11b24443e6d0d4d3ce3404f4cdf14f4f18108540d40c6f8f960254c87017ec19b7c0bdd2c66516933c2614a9697c438593a9711c6a20fb2424e28d2acb82f5dd0f939a79de40268b31602f2612f2117732f31f606e366e67b4869e87979cacb1fd93d945cb799794a8627d8c926a6b0dec58daf6f95344930bc80d0d8c5be88cbc762545133a02d1231317887163055f78db782f084fd2a5203afa4c1e4f9d94e7f9987399c01334fbce9921c0c639df53a8f4d426ed4b83ea39118e0c085e83f837595b09765ef81a63be2cd464b9b84b0c5d485ab63792f7652301fca368490ea8f67f3b5faf424ede39842399d52a7c5356e138909ac17d6abda1b01acf944ad040b75dcbe1ee759afc5189ced5363d099d6efd2189806bf0d5a50b74cea5f97bc33a395e1be2721cb78a7e7caae99b2d9e84e4f7b6a4604e8a916d9217a7a098c83badedb4cb58e214ed1df9312c1ea379a360a6e3a5b3df4f8df42a8e67bd564ae5db6399a68a40ce1bea7046457ddc2c65f90c9f6cfdd1fa195058d101bc043379e7b6134a9f62d897ec9bc92cda10162897181e1453bfb47f17103377175ec110f10302aa9697f1ebf9c31887cdc20f9d7708d3a34084e4abad00600f5e72039cae9353ae5b7636d86e62cea4fbde7abf8fd7ffc8bb0cce0551d9a07c416012d5d1faf535cbbf187c6cef101798cb7e647c79dd48eb85245e24e31848b92e8e450cb781d46ca18eb82a25db18e55ede8e56b91066e6c27a1b100b10df85ff9566ac35d90682be71f04675a921c3f0fa48d28db41c48757189583f9066ebca1f215c24eb4652372471224746769433f4b7aa6eac0a79b1d2c01d314e88b41725dfcea9e7b13858e7c546e4719861b5e3589cc7592d7065ca774b26769eeb93a939354cf6e79d2b35832a263f0374c4026aea81645a20923a4e074dd3fb7d5e2df94e8978d87192b80e7cc167d579a67b789b9e9ae00692734c2202f78dca2b4487b99ab96106214075a77ed409013160b349fd33ccca239788f4b218e57db8fe98935704f9d6765b937b6f99524a016a0638061a0626dcdcb091f223c1849b1b3652caeffbbe8f256340c78f7b74f08efeeffbfefbef53c197d897335c670001086684a1fddb0c0b73461a43d2104388185d62e8b2e5901a0e9c11e6dca49603c78623c16fcec963ce808eff03e4ddd37264e7040c1f3080f00e772ab4d404f7b5cbc36dd3df841dd51f08f770a10875dc2b39803cb05f9ed4d1ece940bd1d7f20b1c57ff1c56742a1f105d01764889009238630bcde91ecf58e919629d944fb773533ba2daed4ae3db9d9785e47abac5590d46eb3b1e11e9a1a36a8907528c2d44ef37c3e995aa1ff33f224e7b4e51dbe4da15d39936d9dde9f9d54fff5f7aeab730abf6efafbb4b723b5b7d1c028a8a685fb553abd0aa44523729e502e3736f255c80a59c8573c2424149eaad0f1fbbedaede3fe75a7d38c8e2cb4fd947617471db5bfeac1eaa654e0a4ac92e38f637491ac10d5ae3d096e1daaeef9f36cd108ab802e9215a13a5b6419d69491c2028b94ace9a21509d5a08a8a480a15484d90e95263c31ce37af14a574365060b7d966bad0c7d99218926444d4aa46083242d7810841f78a1c5eba301f5973ff385eed32474919808539f0930953f47d421533eb89f9ab6bf0cd8dffc3790b5eaef3ffcb777fc39f46c66b44779e45674ab01ba533481551c75a7c841a87e2f526e6f9db0abfeeeee66ad4ef77318d4220a56f759ece2d102ea21103841465691ed4f7044fb2d0ca3e0bc36204d5b5f310f2c9f1f966b99240ca63b0ca85b1c51100bdd42e24f2ab36a863425a9323f0c4395692a4f89557e965dc43b38a20e36825fc58390214041cee10429c2ec2bde910515d86b5f38133082845f49f8b5fdf8ed0b0788c7b92f2520eee1cadff55031a138fedb1b793908b48545f8b57d08374a287351110c2df2be7a59c5848e2c160f170d951fc8f270e549e95af9f20223265b386182830f9830434fcc9430bae105b444d0226f01ad525187a82298247981430c0931da6c8a2e37d6b8e0827bd80c1e685dcc3426a6a3e66619c7b158ec0574d48434214d4813d28434234d484b525a3a0df1214d4d4d41d0511ec92379248fe4916c9247528c06d362b5bf43699a4c0a12b55f4b6209cd88659a265b424719934bfaa511124acb1eba5fc664acfbe1b8480a1d7dc887868686868e2412325286e35ea9ec434a4b4c9e7812455114a26327755227755252d2ac93601e732125966921cb8a64321915ca62c488e966315dd4464056875f9b7d5ea46c5952123725252525cdb0b333760f4ee43dcc66b3d9ec9be3401f7790d5464c52c62d66269bc966321908c5365693d8e4433d6b317dda828edcd446b5857ac7bf5973ce84386e9a35353539a163cf9a646d34339a19cdda68d646b336aaa2858beaa502695156e447558a6a7462a118c762b12ff648b63c72084f9352d217819eb598d96ce6039d71534fa9d23ba35f91bdd3d4d4d4a4f1e0365eef2d041f3ae79792a5c4822e3145ddf293b3b83bd2124e784cd55ffa4f1eaa3f752425cea8cf85c988734f614f56df1d38f9010c2d3e507902c6cb6b880993830e484bc0c87879c7aa679941757f2040de62b9ff2865dbd496015dc2f3aa7f90ff0ffb53af41162ebaeade9ce9828e4fc52460b5bbc51e6b2a3032a22e92510f28d44542a2a98d665f0ea3a6da6fd3cd6ccecc160901b0f2779df25a5465a15fbf5feca7fd5fefa0fbfe5c3dba858491e052f953f809a2944242228903fc43dabb978b2a1586aeeb00afeb84508b5b5f84253abecdce28be1875291346161c747e88e20813314cc6847931b35a045fe0ca29d942250535f5848e2cd63e2fa5a1e55df18312181a20b1a10b11927ca1850e5ebe9039f2e267b9cc383da98139ac08072a2120c03c390179608cda2f87b8825c41ab03043a7252ef8cae309b94d49cd42e208e6553dbb82f68b6252d9d240f50f2371f2c8fcb6ece1e7828338c6631f88a8166559a9a8d58825253405a525216cea2f603718f0a5358f1b33a5be8d25c9e2b0b3a725290b7b4776e675f028211d1fd3456fabea419868ebb9414b4e44bbee493db79948388277870a159111428d8105e789863ae4c19a30c116828a9e1c5529aa85451c11530c4386101b74b6de3e609d57929d50ca5a9a9617363e3c68723049c1c22782390f06058420e00026082785212d98a954f3104a0cd3306c09dc2d0717242a73b31112075d2b263e6948545f3c309354e4314e0c60c03dcb081078ed3d2017082b023020e5a244c71812724144a68e201c0153d2628e9b19a692060006312a0c30413174214b023099f13c0d40da99ff571aa32c451418e5e95a10b5efdaa0b5ecd610f0787015c55a16be8838eeefe2a40dbc19624eab84764284a4a135b78f0a62d3070d896980c5a80c0a44121434b1972072d6060d1a245913c4349092623c6d09310d2088aa241ff8088256388a041564466465175918ace00e2f3a36509475da41a54515282dba54b5e39fdf98dd4ce9de537b5e3f6765e776de3368e97d3aac7b59bdce49d60ed96e372c8743c9bd652dbaf2b7f530f20a1b620f757caef1c9e615237f7d6c2e8baed9dcda5c6b9d4b6e99e0447cd015ea68cd706c78deb1fb77656d76e827543ada692efd3a38d311c35cddba4b671f3d4d90047f9a36b1274700c6b7baba9b676edf6b4dfe11ad1989b01c74d29dd8f70dcd4344d6a9aa6c921dad4529e724ae6343b6d9ebc6edb36f9ce71ee477a931ca76dd2b9b9fd697b6fdb50506ca454082b079ebc79644319e12842a13a5438b6c671a739a736e79c9afb9139e7e4b8d39c737b97ef9afb9139b777f9ae9d360e4706ce599e0e5589784888631cbb21168b7152320c87189a3d9d2a6f6bc8106983413064c3ea9cd47296a7697f1bf26cb6342c98fd96fad115f59cf721e0e800a38eac09006ccef2c8d6fc7d9e823ad26eaab750b7ed3bee28f8888fdab7cd5f630e4cada87dc7b2dea159f106cac8a554b051fd3b9ede06ca147e7c64dea2d9d594d33768a47d88eabc595b4d85da5c7ec76d2e96f86fc2fff472966703b5eaededbda28df106e4fafb7701db622fbcfdee8582de2c5753cd9039af76272f6779bc10d0d042f7b9ba485a90b0481951c20b2563b89891c40ec8a000151fc098810c626019c3b3c69ffa7fde44f51928af161d5633852f4c475099bdc00ca423236231a002043249cab40d5494e1b85c314601a4177f9afe946159c240d9cce74ef4740ac7198abd85e3d4c291ca70fc3c47101a70018c0663ac74d1e288176a7852a6872874e8c2895e41c784e41ff0062c4a480cddc0a4f4f22aa4f8f282284561c870a56ba0a288bac9b95ed708b3edc37dbabb894d88a20f075158575891a3030b98546c9ba2a87a09d5a7b8e2ca0fd5af88a9fe2216251601665dfee5dcba3fe5d321b24041c3ca1cf2471bee2d2d85969d1bd450fb065ed4fece91434a29b75409930f04487e100b42727c5240116d727c0e63a490411df748f640bbba48520849116382c287b8028d24a46e22c9a8282926abe8d6a050555d24294775dc230d46bdba48528c48826d53930dc4e76727bd51655da4288ea8e3a7044527a5b3740f676c40f9bd7bfb6ec109ca5dedb7d5b4517a926e7316f5755df8a8d796ce97eb3352d0df5d71cf819a4bb947320a33f5238ef164c931d6983de6dfd88c21c52a33b37c180c86c4a5b448513c41eaa272536cafa57c6c134906f500730aa08e9e1529aafb4f2b5d50ae525639d6b0d8277a28a223da889ea882a6888a0c3d14f1c95c50060e39246d6162cf9872840ba4b0610c2b57b05c2ff607c160f4be9d47d07904ed7719c5cae93c74fe1cd782f6dbf373c7712d3837d7addc83cb812d6ccf75533e364db6e82dfa635552f08bfcf493610a411fdc150ab559ae171d00b6e01a4e037604216838fa8f802b3f03796b3f5a554ea890fe21fd40fd413958fefead08eabf7f03a338dacf2dc4d1c2eabfafe2f222fd166c715947749bc34d786809c788c4c0af493f972ce22fc3a9bdd4ba05696db0bfdbaf4597af9a7ecd44dcf20736c45afce9d06b7179d8ac808e3c2acb90855a5c828ed3c8a75707357862071dccd27802a380172ea8c4844842c394f6d2ed3e4bfb915535d0ab9a5709ceae5c3b97a0f3d83afc768201c1890607ffec20daa7c3cfb942c79ccaebed8218d28f862f8e6ef0a18b171879f162618a0f4f344162062894bcb8cb97137b917e43be9d57bacde9eeee6f2716cafff1ef24d2e1f0a7981c95808b255c2c1dd14107259a2441446c07a32364d4e0880f68986079d817e9e74ac21b81b822a8fbbcfbfb22093a1c7eae29866e73be8e33bbe29f93ce54563999a9fc3a6287236498b1d4658a178f6912033034fc00cb931a5efc2c172bd1f52ffd7d917e24e83667dc9cdff0690d3a3ef7df116a0eded12c0642fbf75673c85a1d3f81b53a23d4fe1a41d0ef834143c201a7fafe832502195b98f144145ecc107bf5d198264ae4908226b894795560088d22aa3c09c20c244459b4a04c0a60e04452172fa5ad4b0e8072f09110ad21bd6a11e9151adbb32d8f0ddd8aa157bd851dd81d3dd89d0d2c57470ec345f5caa190d5e9572141478e31713fb26b79b6212d1c732d8fa3c0c33dec7dbf0fc7c08a2bab335f4c819bb25942a1f0926e09f115d3e075a9946c718a3a582d76425afe98da622ab9ab51a5714e2ca6da42298b12baab93824f8fd571ede0d1222b595922fac2b4c3ca0ef1bbcde767796e6affe95da7efdd409cd06775baae8e7b1474fae9abed821a514286c887981332c8a1c90492f850a64b193d2cb1c309fc7de10499da0481fa838ccc704318494451948485ca8e21a00c816485942d43afaebbea57da515306754df394fc4d7a1ba77c709c8e3df2994bec8e41873435f960b94155d5e39fd551a18ada4b35a250aee326c9ddaedefa5df5ff383917c72d15a11e84a9cd798fd1aba3cb0be2e2dec53d0e640381a4504ff9f8515d418142202d72386e123397923fd30c95121857729e3842e484ca714ae99383472b25f4e5882c570f1dcc6affcff45cb53f04eee19fb32925daeda60c50556f753827f427294907f8bfa469343c65b52b1e9fa6741ebbb3e73cbfeb401b20d0ad3384e175e8bcee0518c0ad2fa82a0c73db42c0657e40a011ccea610a2ddaa441bb0ee04b30c1002f533e26c895c1e5657715f7fcdb3f13c702fa9efbe738830ef0284aaa285381e07e5316a0212ccaf62888c2bf299c1b21d67edb2acfa9f910c28d12babe3add98a94182d7edf265ea87b76baee2a694d2f35abb72c6d1193d1001a0f67f54e8388564d8b155a1da2e69297566770767c5fe31c7ced762ff7ecbe32f25d87503f7359be886628b523af88393a38e2c1556ec7731cda31a2e472c8f5c9e002c8f7f8ec94c8042de5a61999c38a8aa0b04041db7a90ee11eaff2354ed5504247968932998c13d3299d30f37de3975464db6208e2428b3201b12064081130b14db2887ebef64720e887e52dee39707d253de46094c3e51d628b3f49494949493fdb18d89699a1fefb5b577766d7348de330b0ab7e1d8c33b59f431656a71fa50425d23b3d60f8b1addd1efefd0ad89d6d47fb4ef5187964f1705c55fef9dde62dd7aee2dea7bfd9b32757071935276af2eaccd501d2a307f783337bce96b173e7ce9d3b7156e8d79605155be49f93d2eff33df269d70167598d46561954d51a2e545ce1846d4d296aefbecd13526adc40479652fbc5d5168e2854ee89da3f77571a77774abe06ee144e285506bb76ca87282a6958e48cecbbbebb7c43ed96cc5255670fee16122742fa024a6bc314d11c2d24a8a22f906a8750fb798cd1a4f6fb748b5fcd4e6ad770a1e35c1e146a7f4d3f13749c9ce24dca4db238e796829315ba3abc531aa734e6ee6afc5de966d3aea62d8b7b4e75b5a0633ba9f4fbfcdf3ba56e3651ed3b175050bbc7ec75eed1ef45d5ab409b1950a2ee51dfcf4d1824d2aacf3d95aadea7420ff45687c5140f578b3db8888b8250bd89bc3e55e9b4d4e86ab4f44bd50af8999999a76c51545ba08bb40511d21631a8e3ac5e6a79b610aa3c06111862985e777777f7eeae97c95eb8a9c56566a2010c7be126662202432bc9a66e7346a77142f9378e758b897ce545e6fb3392dee19e85b88889bce54846875596552e6466669ea7d3e9c4343a4ceebb398ed31c468323f1588beeb0163d22e8c84baa7f37f11997450b21092c9216b0ba4bb3ca2fbbbb5d2e31d5d4455ac2d2537417f17518a477bccaa5820c2c5a3006105c56a0840a43060c49a9c9141f340b62556eb14493850db597b2f0b234a5f66fca59b0a5899c1e2c451c750d605ce89d71999613e22d2e04724964aba8a31131a877be8fbd3948d0b84a15280c853029b17cd5cfa24041429aa05f8db840c911529040a2e88c1c5e0230d261872fac00e28a0b5eddfd1dab698ebabbfb034314e1a5064eb0cc50c30b48161f4c2126063d882df1ea67b9b82bdde69cc07d79e7de068e36757b8edbb8d07f0b998a425d242c84ea16303f40b678a9bd5140c103ec351385fefcc7a19faff6a5828276c0cb171928f9024c10493e60459534b03c098215315efd3e3f3ea0a91fdb6bbf23574ee3c2717bdf4617aa0b36553a98d3224b86f399a040fd41fd3e3f5b959f53e3a75c5b042a88a0daf3cb658e526bb4b0d73b3355fa3abb87bd3a5ebbf6bc6b97dcdd5425c327aa16a8584059e3dafd520b3ff451affa6bfc3829fd6244746c2f474f50e11eafc665b2af11baec24f3563735c99a6435344d4d4d9486d6d0c864b23eea388f99d7a779c66278c66262b126b7a17766d3f2cc88b13c5fb8c7ab799a6f4a8f4e7d74fa8ed6d0f451aa534dd3477ed44735a064d19f069c4c7fc4623931be01f7c5365ecce606b401912049f22f8fb206e84fc27ab9943da1df9fec01faa91b7df3c471a8ce461527749ebef5160e7106f43a7e52fb6f23828e5f6df9fedf27ea5839bf2d244b4a2b445ec89410651edda0b34f53083afe9651c31296170ad8055cb4d7298c327f5cd5fe29c39156f6108857fdcc2c4ff9e05e82bf3acdbab1a272932308219f670c2d364784e57af1024012580aec8ed6454aa9edf60f43a269d2a5cc1112a389749d4ddb34269ba625fd284d2198bce0538fd39826865e28e82e92bc4e8f3a8551b807e29e4e52fb67113729a03dea2275e1a50279eb6366a6b1d7fcb91fa5cc599d15b4108236d80287383f2ba7fb6befe312e49eb914aab8bc455368b14a091f0e09ac01f87bea4757efb4d594807c95802acaa85b296fde06e4d70db82ff74aebb7abfed1e6a5b4793fea1d7fb2ad56da5622a2767b71c5b6daa8b7a8dd495d6ab76703169935081df12a62f328e826499cbca248556d8df96606eaefdf3dfb77dfd9f8efc6df088bdcf86ee004c1f1dfe3f86eacd02f1be10fddeccb3475faf429ace1e2b2e599cb43371b102788cd8db77923af1b1027c8cddb781b61111b7ff3465edd0db06160c77c7503c409f2fd8dffc286f9aae7db007182dc781b7f236c1b737ae10afdeaa106828ead349767e6fbbb8ae5a9f9feb6827b3c1afa4da90bf9aa7dd4a8315dc85b2d8659f46f319e6a315b13f2ea788d0736eff0a701e5aee8ef42a94fb9504d51703aafab66dea767c2e521591d794835bf471e3f68eab4d16ebc17641d7948e9467b1b705f9b929a6c75fc899b509b881a33a0a3cb768b1055be07fe588ef37c741dcae394eceac3088d82fa4fc98f02c71c14784af9e03894c7a133c02b177aab4d97b9aca66f96d8c5812eefe022d4a56905a50c29a5949288c2c65d62216fe5cc206fa1bee7fcb6ee6ffcbefaf9f8a7e1b0fb446094ba213298bcd8765d6a5a056ed4aeb103dd346d4b69cf9c0455554a7f4f6153eedad770f16a3f0b18c8c0f2acea55206ffcde6fdfbb93e2591ef95c379005212d3610747befc74d819b6200976140d76e42c94808384ed5401c7eed650784c3611015155448c8428b8d7242bd70971e0cb9e7c53da8ef4f21a777bcef9e796c8af48eff7b1d6bdc1683829676e9ff7b5d351aa9db5210d30bca781011a69fb7508f02f7e581fbeab6203e803620bb3ebd6d5aea47d7eddd5b2160580d28b43d905f1d5884994872e58a97f728b0484e9657f70da2c0a730f1427d0726d95ea850481f8008f74815d4fe790465e1099df932e774b18b579d7200b7c0fca7f7dffe94f2710a4fe1c83f02ae1b731364a1450e705e82b6ae977e2231b86b01ae9eddd1a46b975c8ba5b26b9ef6c4aeda9e6b09fb91b03bdc7b58a38edfe99ba7273281ac0e50901991d9de12c7f5f0424a2925b364aee103dd694257e9935c900fa3feeb21b2bca0cb3b50a1cfc9a3363dbe8f8b239713333333200bf5ca8618120e7906560199253af612d5736d71ac578dc3f66c4b96875b3cd42beed9429ea161b03b9c825ed24b18468661300e6c46618ac0c864b2a0e5719655c0022c08e91dcf0667148191c964aa1fd1bbefc4ee65577ac79f61b227cb138457658a4a9502e56c51d4c1e2e182c118c6306fb10c2693c9643259aa679a500a5a9e9eee734e4abfef5fbc01824e5f6d31f88a11b3622a57ee34e7e9347f797578f8cc283373dd5b85efd334c992b206531857248b876bec3182336f3f50e78d37f6707987162a4d27d69d3513838bad3285f8e1de7955c1c3c5098157ff59de0104a4513450d65458aa02a564560613babe722d9df7cb1302ae2ff8e00dac8e6b1d08b303238ac3e1f65b5fe03a7edacbdfc21ebbea9f3d7af4589d7e5518745ca61ecd2499846c3374dbcdf06871db541eef50fd6a5aa7699da6a9c0cff171cfcd8dd5e90789f4aa5f822e0ce1c0ea348fcd6118b3897adc4379473ff3c6bcf1b3b7858e1ce6b98e360199dca8840d18353d0ad1d00c80200863150000180c08064422a16838a24adad80314800c70823a6a503a168963b120c7811403510c328618400001c610629041886a0d23f02d2b413ad4d9346a1f7049d14ab5485223bd4413aaa58e048801b348c6eef71a829bc85159ee6df65a99d0db6247dd70b2ed6b9f90cb8d93edcc7dc9a5d9a6c96eb164bab4e2bfd0b5b6dce7a77ca5623256a4776a5b3c758f62fafff21d4a96615c901971d282cf349b3c2c754496b03d9708ef98dffaf6b299fdd65f756fadb0a5568ba9ab102e32d8275cf4d258da179f01f36f04abb2c440696b9e72f8590ecb6497f1009f65fd2e06d68e96ca7cd87ce9f28b4f94f4146f3e28914a9951be2d6da9785f81cbf5b13cff93f90d7ace881600a1f22d2449b93d7b75fde42c648a4d1f32f3c85dba41fe560a9efeb9c332459eb990fd84c4b0130b69d8c4eaa6b51ad1a3f0b995e48b9d3f596f3fa425f39b5947e586af6fcb1a91ddc7c876e6a30b04509f6ba03b1a7d4dd370b20b4841e1ec849c11a87a77f1d8faf648a682050a49b062ad6cd170e5ddf195a564e79cd79dfbf9c52ae90f59c54b0fe6a13251a1592adecda481ba5f80f8bb095479d0637f5d86c7469e745c57d29755ec91acbdf8e224fdf90023baea09dca54ee8f136008864aa117603f75913001dfa74b2c9d2cdbfbd927cbaedbea96d027a3fe3ad91b5974b56c053178a1fad7be8adb10c1fba2610c03f8ce472efb3ac84726bbc40d9ad4eff68cbfdb2479642b2e768fbcbacfafe4ba1765a6b1f3c36226493808f67b4fd40809ee44531be8514247cc25c970b986069b73cea80b73a1128ec830302bb4cc85a66f21c024dd67275814f7fcec206a6c4a6a44d8a981b41c5e0b02e9e193be8bfccfd1c029c278aaecc4233a882f368830c4b1e162c3432e5e0f2b9322a522d1d28e7c312f6da14f2fa31d65b1142222198e0c6378e0ddef83c2b7ac519e06d946d9f468660398759a86029bebc246e3f5a9e24df2c35fb4cbe5a4d5dd63fb40faecaa502ad965ba4c108aced29e093c59a38349893e55d880082e518ebc86a5942818b2a81936a751a518bcb942730140d1470c20d989eb835281cc129a920193779f1d031b47fdbd3be954780c78e47ef2f4b54f378ca255807c515a940ab17a31c42a8688a11fe0bbf8223d0fc63b91c07159b30099b577817cdd600a9a2715e17af53be576d605d90073cb1183d4095e604f24c54a76f68a09d75d9584075c42c8451d6847c974f51c62dfa4c2c72c672fce4075c07408dbda152daa7ffdd5401fa4b4ce93b44d369d128f0347a363392922b58aed65378a44254586bef378a25a1be6a6423ba83e8483bd8e338dc51631445611315b40ec7e96774b22517eabbeba2ded85386bfec6134f555e83d169ff49a7fd9a603505c217bfde170ccdec3a1df240ed0aca0abf97d05fd45fb0942d4f571911cfd922afe260ee578e04737edea82f1cad74993917ff840cf282750a696f22e4d766d368521210918d757606a2d40e4b80f8af3619cb009ae717c10f4adea5c96faba407a2c85074131a3e321c0ffee84cf014c42b43af85a39bbbbae38dc2da69b4ef2180b3d4d6ffe7beacf0b4ed7b2e5b9113546673c9c36859edbffda5b95af324180f0d15019a309018884041dd265aa3bc09b3ff20ec65fc5ce563a8d993d1253e907a2de5ae17fa5a53dc3f6eeb3ce4c2979c99007a519714c28003ec369a0b03416f3a48720920ddf5706b98cac401c4d63fbd0846cb616ce10c1bf059016c849cb5923ea7752620e915d91f0499594754b4d0673e8509f3af73df429af6860a533cb9fdd7587a743068c1eff63f835504dc9163f24ea90b0669a51d2ebf00172b3097fd326e930ca4e40169c30e489ed94a814dc275689265c2d4e76799c99404546e9d668e34cccd22a582ad2812c5f10913122c243510ef6d2aadad160fa22c0552e4469d1618f60a1f563094606b0f488668c024ef4eca93484481b80ed3f879d9a3ddc2a54b51469ba97c9db10797aa7225d9b5ed0867cc5f397ee0f434ebbb99a1528225ad34479a91edeacf6b234e6a99344ab2eba1b6ad9120645cbd852c55d23570ac4f012b90fc95eb2978f286c1cdfbaad4961018629621fe3d494265e71d7cdb661ca37dfef066c175a2c449bca9ae9dc7c7d5fc418a46101e7799e544c860106d51541d5be9ab843e3aeb602fed5c6507892f6ce5f9fac28354caa44f06a692e4605e96dac8a41147c09818b6b2a5e37e23ea92050df769f4c7a6ff7d3c7cde3357281b5c055bcc6a53c34afd10110c1ecbcd0c4baa4c8d40323f36865cf18b0586ec496787b96e642755d5b74dfc6340fd214c10fed6c1ae727281e26a109cdbeef27b020bc7efe775a51df5c163d5c018ae027aed758c36ab8e3f0548d9f27cafc73e6236f0b7adf09cdcea964bfc6461b036e0a15b8aba72fd8a2d8b3297a6b53876951e20d14a58542e1117cea40921d180c156e4c1dd48bab1fadcd446fdd7243bd4fc58eca9749d4e19b30b1b45a414de2f783818eb861a81219a6f0c181ae55a6dc3f9ae52bc953200d0424ed1800257059440025e196e3a1280edcf36430f5df8fb000a1a80b8d564129839b2a8561990959972124f13a806adcb90c0ae7b346d2a1d41cfc871b9dd7659d57be8961e11513aa38bb3df7c3c250092059b0db44ce579d4cc6738ae34612089ad301e6ad88a5bf9d3b38d810eef437e0ae5e42c16f6b64bffbcbd4d1052eabd1aac5e7999363da1fe1b37d993094ae73687fe31e6613e31ee6137535d53f312d382a1c3c83e00444faf3a8c403ddd563f3408ed1411847d8139f031c26544430eddf3e867783f736e53ebef6ca342acb26a2384225d6b85dd9905f1d490e670af61866248374a1e5741fab465ccb539940a81ff90ff207d86f5652e5e2c4b5bd1659adc189b6a47f362c7717524eb1b438815711867b790dbdfda6854b7e10862b4da8228a07d08f87980df30d9bd57c3f20012db0fbf97f385979274af5a366e86dc254eec691b8d865b138d4d6ed5e63bc06c36ffabfe22b8f8fe59a9fd839b1b90dc4bdf731bb875bb5fa2a2492a1ae7181a15adbd5d6501f99b69a4e8248f9b24d78615bcb253f459676be57e5997a9ba1b993a5f9f2f392fe35a65afe7b15acb5fe47d780234b0dd1551bd7367a3b8c5de182fad30a1c47a2784239582598f43ed773025e2a519d1c3011020018012f53237ddca119dc96787a8de64d60269914e7f016ffe3b790886d22f26a218aaff115d12a7d1930b165da96e72e113268466ee457c8d76343ccc594c3f6f60e73c9db682699b87ccf77559915b7851f25c98326bd14f6c265757298012870d36c0abc2484d37a9899fd95230de26f1184072aed2d2a41fb545aeb940f1e64ed21fa77355dffb91dfdc2333a2276273cda1e2d124a37dbbe914c88eb6a220a608fb999d8db60b1f876a2c2f5ca45b567f60ae6af8001231d1991388f10d9d8e942de932a9bc0b1ba4b08cf738750bbe4370689f3cda6b2b9400af2599221f068df916eeaed1bfa8e1390edebaa3414289551897fb85b2e81b79208b9f6e04a356066b44231b995c2a0927c4535eb20c47a095e728eea4366d27d28c503b58c7e695b6a3e2cb131bcda70b149739a5b36fe9b1189088033844e8a191debd3e247ee6d3e1f6a3015ee9826259efc0cfe640295ee7f4c563e791aef688e1f1c29963f5adaf92988905c43ba59fd718652f9340aae4770b04d99919bddbad9e72a81c372a889672f03f8483136c091b81bcd20444cfa816c61c65d456a3b947a1cf7f65a21b1528f923e24efdbf76553b775e3f77b0ff9a680c8e98cdfd5907a87427d38ea7f2eeddc90d2161f58ec601f372450f75e3adfb9fdef3ca70a509595f3aa30eb58d9d84e96184b94c2449fcbc076225d220c43d43813ce11fa7cb86c40349b0b40dcce738505c2eac49d42cc8af6bba4072bc4546fa93d2146ad6081df8461d34c55272d9a88e0f91a147a92b0dbeac409e5a2fab7a2d34a96b29aaafc6d41fb3f409bd69850cc9cc24c4d7a42b7c4585950221239c90c97b55a0b49ff0925a2f16c25141a52aecbd4239e79614a334845c68c8da390304e5bdb733e18cc2e9f59e7e46ad86696e3fcb8bf22fcb1e56f18786151ae1a82d2239791e5c172be4f33b26b9eefb859a51d13b7f9bf543d2b1dce52ce69c50132271a34c4bea4f452504370e52873c2b9c7d630a036c425f01f19979aad40969dfe27b5b1df68993e1c1eddb6816c1a15aed77c3fd7c85a570d84473855fad6aea474cd1b136bc11962bf3ce0c56dff7593a44abe4b3922a4c9c7d575890364bcc24a86ee2690d6819f39a3b6a2af56526393ed5e0e820ddc678542a1110e2e770cb45a5279839441f7d77609bee22f4ccc5aeb72df66aade420ad8342b0b8e295df94a41d7837e7b50dcaeab4288f8dcd1fafe852d25f71f3bb11930cdc24125da831be9ad091c1b4bc99da0f53a8d3d911a58de582637620a652c7e93ce100cdff11a833ab21b55b20241160cfe083321ce34727a01da51e2521979e256c8ed2e1d5205a131541b66d0a2c5cf1f11b7688d88b3c22089c80ef3fc415080a03205b91c082adbb1c715c38d966ab29db0c6c38ec1761dba51eb78778d581d41555ee31b85abd93fbb6ab8c3d15bfe538f34c2bd1b875a9f7b7a512bb3939c075bb49dd9495152512097d3238707776f3cb83e1793d74b39cae0a7aee351ed25100a9dccfaef595191026376df6709f0833a38068c86d27582d31eaebfbfc010cbc03b32b1c093e148a83481878c2501552127d1a8dbc8c18f79bd362eadcd98b048c23559b52dc9d99edd743b3d45e613cd4b3ab81e2dca09dcf46de8d04646732b182e0953e76f91c6784dba58070ad40e7223708ba7bb32aef89d24a04bc8ca378a1a99f8129e3275c8ec0af114d68c90090fd2a9cc99fda07184f13a2dd1c57418edc749136e65a14213cf759e8fea64baa12113eea2f547108a76b6fc8349b55628ba422d5fe3d3ae1ea0c1d594a62edf850f2bfa4a80635bf8646b10e00a36edcdf6a74c2fec8d89c9079dd3a5f3456b7dc4636eb6813e59876b2d94b8bef9dd8c56fe4dae1867711a5917e67e34ff8b68b96d203520fcf6ae586f3a1d978ad50ce49656181f05edfa4a12793e0d4129749b82e34ac6513446b4b34ac3dbb2ca1ce0a9c7191d92172ba5e23dce373b5fab0602bf10283e615deb2d67f8eaae3d808d376f30b9c81a9ef76535fa012350dcfec5def0b36f758395a509915c301181ac9f70ea545dec32ac964e26fa675001fcbaa0dd2c342574011e6d9ca5a2e5c09b38b5373b68bb1a409e63c46c40a87814526b88b40aa77ca276ad8638918211c737603d49f45e69473d1a885bd47b9cc576c0101495d05726b4eeba46a808b1d0d654296cf56cea84cc943b00d2803c57b3db8e32fd4a92db2cd22890da0965a7f824fdd11ef970f04fddbaae3204151aa9e9fb65e28fc9b1448fae1cec2db4d9e86ffa57fe6d4493b1eb1add75e04745805f3abcb0b029d58bda1237b4422ee9ae6e9bce34249d583267466342ac6d7196fd404023d96913263b6337c894f60747464a4f942e88e0c2890ac2648351cd2a2886c7d580397b18a5df1c40f8a8290bfda07ed900f42d561ef591bf0f4a549044fd13b04258de069a5f5b4aa4d085c9c767c59b84fe65a69099b216dbeb5c0c57828d1845aef5a6603f288d699c693eeece3f041fe5a5079dd1e0f4b517528670683c2b94447831996d3cc28147117d0381ed7061d4f66450ad668c81442a517eca05caab062019f98798018c41c032ac8cc9538af5c2c57f634939a7b9617e1bf5ca9c8b825789e47595fdb71ca3b7adc8b8a800edef0d4ac4007f0ba6ac8dfa6391430c6c535580ecd2378f5d52bd8e3e5b5ac278362a64f8f256ae60ae52996ebde9da6e02beb6daba1fb9ed2aaf45ed56a7ccca39b045798514260feab5c48328ac6a6f578c0323d82793feaeef9594e3cf58b0c6985fe9c0921dff918927edd9cc68726fbd16a42adf86f372a48c1ba814d47b115d0523eb312e0ead13f0c120989a103043ae7f9ec617b2ecaffc5418a2b3e4fe0be5539e90232e1f3d48e82b55596375f3415f93a49011f12f317789ee2174e0bcd61175fa2bd0ec4d35058dd7274742ff8d1419e4ba8a1f8e11bac72055eaac84d7e6e1678389f4d3a8a3bab80fd874bc8f6c82c3521f112e3f6899c84f5d85e08c53dd47ab372ddf198769207056e9c877fd7a7474aedf4b5b0823e4c3899baccef49630ddd1f356f9871d1e9fe79c62d9d07493555e16a7cebc9c73b534d4720a66e8facbf8c434fdf2e357a0e98d613bad5eae220dbe1c4e43d2f3a15f7f35c5621fa0feabc4f21759df9185fa69798475a0c49d96fafd64f1f6f19123722bc102cd12ccbd215ce30388067a3081246d990517c70a7a15ac60d3fc5eb67b9e00f1d8cabd10fd10acdb9a7cbbcd581c6284a2e238131acf1cd306878423c181b0f039878eeb3ff7e29b806ab69086ac6ad6e64f20bdc29740b73c349d0f9bce312530993c088d1b153871627c169f260927838dd574710134ac63544b519c802e79d621811cdd8a6b30d7d1813e80fe1c4bf315c0366152bb55cd4d560cdde2e82670691590bcc99b32e494c2177ef082133ecab06cf1e1afb6e7874b0f892f57fa645e4512a38f17c5b5bae4593f7c2bfc1cd251ebb53dfd8761f2e0ed934b2ad1ad617173643b738b91128911ca1a41763c1d5826b02427e983f38941b0048a338691d3896e90b47467cd5e14909abaed54cc0078e2e18347b2a6963c73e4b6baeb16d9e81c329d623178e68828159e9b625ddbd0b7b7e9303c849d076fe32b65b901a487eb8759245ddd75937de555e5eec6ddfd3bb442a149236d3d1c0180d8478eb50447862c75433d802b61a671e5b0325c0e3b01fcdc806b0154295fd574bbd4b55a21255cd5cc39a344637f76059ad2bf456ca529c94baa86371c6e5f228edb39cf144b0f142e63b340439f4c95534ad4c1b3b72eadc95d20ba6ca38c75f3f69cf88e89e7e119b2e6644d37d017a1dec1ac2dbf114cf9cc0716abfe424a76f9bde2c7996788ce9595b029cbe4eecd95d56e14a0eecd513f70a71b326360a73f1488be766a245dc37503dac5de857e8b0b57c374c517b7acdecf105c43c8ac4baa05a8fc38be235a6796be2ba6b59f2c0880a2a935264f78929cba59320ba0ac0a97844197f029992ee7193dc21f7c08451801e86030874516a064875b6374fa790e4209f550dc1a192b9bea88cd510bd872edb7d354c56ca155ff9597fbcaa80c588467ec9203e834797e2c03cf80294eb09c106809e75088a0d65ba3bd348d367b2f5539d99bbd5ab79c547a75fddaec918f050f4aaceb3886fab722cee08320ae0e109b969069787a894b70947fc6094c8f328be72b76773b8b3095b97433e01147928cf68f956ec1f924c9280a4549fb21c66a7e6fcb013be2927a6fe9e7517478a62af8a17a820ae133f9e4616b84e74de5033d2f9a639f51210ab0245c073077d13f2659fe3c223eabd3f8d1bb0e51c8d17cd8df79f7c04acd3b0762befc4c74f5e93e02f429a2998ca4548d1a6f6bb518638aec25740ee9868b1e1bfe873c035109d4f74362ea2c0e2ee1cb4175ee8335d806d7ff98c0514796ed077e835c1cc998d866b67716d9cf2bb96d3bf2c929fbf85e5a90ff6021104041d361221375a900d13a6e417d341943f63b1412d18abd79b15dc3ac98a70d8a35861d2858f0bed4799c82ec4427d577ea2b884b82d02e2b7ef5cd217d8d80c12256017b926ef623997a26c4a0c9942a890d8351169082bb57bfd0487f6940163043fd80afdc84ae1260e4a1af7da15b2b296475b484bda17b4f4389c813d877701499e74b44f304a955dd559e74b03c2f4f4a0d458759328a3c5d6ac55c154fcc9878bac41655a8cf4bd981c184a06edd3615136c997099c79f3085b98c272fe09fcab1873b730b6ec4500ff3c15a8e56aec50c1698a58670d91b694ec9e0d51a953f5b03d822981e06a2734ea1ce870aca5de472ab34da628ff4324f74bde0e63a44349150075270f8e1f883cd9119394dcb725ac8aa5dea6f4d2ca9d407a080873ba0c3ba3ad26181cead79a6fb0b6c6f851ab156c3de69cbaff28f66b0b01b291f2e499f6268168bd2dc6bdf4c90667712a124cb32533944625d486d8568ae0a6273d44c7374ba319ab471548c2d3cc008abd944b351481afd62c2e7b0cf178c3225f3475b2f36ca335527479ef2518092cf35aa19151e896a4cb16c4235a6d8222ada51eac795b3c20b00c80a6564d6bb41cb548a235178d11acb21ccf3daef045ec16f85406dbec99c01251651912b4adebc85a7f02cd7a9abc1dff35e2b419524e15e21ccffc9f7f8c9fd98301033d19ea4ef8ca4629d538b45ad2ae921bfc707faab883e9188659b594cb9e3d4ebd07218315958c91f8cb7f55a46d7a0c95981a7757d969983a3cc213887105462523df1a2a51e694a183008b267cf156f986732c7131e1f7bb0ebbcdd91efe17119bc1768321bd114b5e1881b92c1c59e027c60498ebb5faa8c76537390342a1ddfe7c8c21345c900dd0444a33cd02b42c94805df5d3a0b5bf60ef5b00a520620a792b59b4f4e25af3515e2013a396a58792916fd4993fc8def9e775b0508683a9d184a2919d5bcd7fdbaceac0841910390861500171d34d65fb5ec3a0523c81b01eb38868eeaf1b330469c67787296650fc46e0a8de57250bc7c04f0ba98e47066a0eb0ef491cb321886f0d9892507aadebcd317267d68c520a268890080e054ee74938e22bd0fbb9c38e705042801a77ac010f2548d8367177d34e5cabad509c883481949b4ee4dc4708a08cc15415d4fed06853f4ee64bcfb222173f4e50150ee30521d97143081a53f7e7569881d35f0d7919b06134cc24cefe327c63efff8b4b02af2027040b04e593d1fe6e8789ec8f8bcf5e616ce006a3ee8c3559477905af7fd282ff9ee85fe9891a88852cc660d27a3176f4d88ed9990af234533f1d0eee8785c9313c81dfaa8bd688b0f065c5e7ea26d136f568188d7bd267271b4ac1d71ec7c85d0eb163ea19b63e0e12ca7b53bdd2df4bdc891d2c930007e6573d726298240e49d73d639ce6b2d6123761770a405ec1784564a310d00b8e12d826e63067da374876c63c7b540e8986ef8bb5c140b87d47dc06abcf77c2785c420068bfab1012ad640ed82c1ffd376c9bb3affce635f22249e8cc1b15cd59cfaaedceeb09e6b052056166d281362a76e558a5d3a40a7c9b67d0bdb854a3e5acc1522b8119ea393e5f18aeac48461414fef070dadc38f775ebf1410ec4929466a68b14b5ffc8c536e9e075218eb698a661e53c3839b7dfc4badce7924fc541aec8ba0ed790009822abd63c8a9ca8dd959e8a006b8ce875f8af58b48b0fa5978300c320ccfb731015fae94d022f4fdcf61414307920a4c8a2540b6bd67ca04a97c60d27e11e3f0c24d4a529487599c384e789b49b1c44c73ee4c22f19416a3a38a751ebf6391621b302abe1576a5d2e5e4dcfd6b97751a86e26da0d9c15306732a936792f3dea57c95ff48986343294bf7eb1f900103ac351176132ca7e2703e9d3a12c9468172fc9b9dd23b15a0d40b9eef3931fcfcac496ed60948bd29f46673dba9efd30241465db61d273c5eea74bd41b8cb56123f203e1bf66a2bd4daa80df9278656c3940c3783aa8b854717833f5cd4eb79f3c8050eb89274a74e17ddf4919f29f0b85ceaf216ebd04275612ec577c9e77ad55c56891991ad8a94217bc6702d3249020ce3b89a9b142b4cdd88c19cf77159890b54ed70e65a43fc2edc30a0ffb5f3e6be06ca306da7a5ee28cd9b0bf562ceff1b53bb615172891643e42437cee68aa4ae7b0102f5987e3f8c7b08411367be1192e39ff9a5cd8db68b89889115531c342cf66b3f4c1b5593e012328484e44d122b26b18a8127c6ebb50f50b1d41842f4599d69d8425da06b581af0bf7c5770085f6b3d3d0e58a0407c88196800b44291d84ae02d4694c580e2f32ee04c11e9db0a23a75b2d965f4ea5c95ef4f4f6b341e2e18fea8f477853199acb15fdc52ffeaced8970915f28dbba2ac9ce373a7090847a390e7e99ab54c3b3d820e339e88f0e5505838d4adf5189880cae34039c106a5f83b0f5dec3aa1e0ef82b8b45335fdeeffd895b003c35758566ec472163f2c36d79150c58c0a3fd01edfa1be1745aa23a0ccc3d007b11bd10e7a085a0c8d0060e339c292d4eb3830b345e5d044d5a0c68c80264cc35c34445e726335a696ba5f7be5eb6f00ffa176d67ef0e6f73a20132dc1e1e0b287cecf6ab241ed0e48a67dc3bdd8c8d9dbf2112816fbe9270af437dcd0886f6bfd11d87927cf2cd0c1cf807ebdba09b52e30f66b6236fbcff0d6a366d955579f6004039851b9631994e648af5202e05024f8c6ec208189e8f9a08392107bae958e7d9a80dccd319122b1149cc5027defcd2f39646ef78f794edaf4f8bc2bcd2225e930896d00e4c030c5339f52d2131f21d3b88df9e0a46b7fcb83ede8f8ce644d00ca80eb359fc97edfa2c6532f9cdafefdd4b8074c4b6d74baa1760336badb0ef92be4b1e2a18e8d3eec9632a8b1978717bca51cf2c0250034f295e252c15daddc9853d935334c9e39c753770fdf6f50f542badbbe0e336393bb89e1eb0eea5c8cdb664ceb907aa745368ce26849509b34164257f53e560cd1d53ebe8e300a2a531c97b7389c52ef63194df6528672954a311759b663afd9d6993b103b003628dc2d318efce2e2f1bcc10ff91ff58346a72d83906d2362055306368a071f8337cd36a9f027f88a61a0e80b9564665ae426da89ef1053caff12f4426b2d343e7c08788b39d31e765103c1d5783c745669a4e521862281171ed9ff08511fb20ff3f1c6372662a40e2900275135f9907886d44011a53f91e350d03f7e7c8ae7539c1f35fa70e3b8509ec9fc867eb489975a288682446da323ab9434fa23d339a71cd4e57fbc26e0d849bf4491a6627b92196de99dcdfce8eb82248d32028f19af4221576060a1b8c984c51dcaa705bd677aee31c534168d899917ef842743fcfe8abdd5bc282afa296b5200fe6b24a8488c6f9f7e9492bb2815cea40fe8b3724767af51b889327d3e545894beb90ada542ea54971c4d74fb9ae9b0f09756ff04ba5da59c387b46fc8598ebe88a848717d4c2379fcca221117dd04f333758a5445568a0bdd8baeabbcaea01794a194fdd5c034d03a0d8ea5f30e99210af512d8aac7454995efe2f53c63a529c070fb3756c50afc284a7a450d250b97cd28d58ec4a98b5640fc4cc68e3957d07eb4009768027b70c5ec2714e70e7e29d817281e8f59d9334d91d69774a1c65ade75a89250a95f18a3d362c2e5a5511646ebb4fe8a1b0ce6584439159c364c7c5079d993d384468cbe0302b0a49bb436c70185f9788bd56222c2e1569ed61422c101adae0828653591f53a6fd08ac6694d542200d46e5e19f0a480c26b8c2817ed94f3ad736d340a8d9a8440263ecc759c55d47ce7a92c387ef1170b42a6d1049b9f56adb0a794c7051d203aa71b833bdca47f3afadf4670c66ff791c7fe31c999b8e22c2021d3992fad6f9be679f28e11cd8050b562f262a12aa6365939234cdfce149c11930d4d0e44be3c312407b22a34b99d409c14f4b5d80883bf8eca507e9c63e914b21d7d4c25c793d2097f4f480f4bff3bad5a830be5a3c8dd1342ba0654372ea5bcb018e8f0b262469997886bde2fdd24e2c17b24840b24e3599e63eaba7c56eb3d6da136ff1685b181595a2c9618388b21743069db435cb26df2de1e751cf809c3f686a5e9e20b57633cba7db00ec6b117d3911776684642778938fa5089d395624bc4dd31c46ee1c6b96eef61942b1d365d917c725e7f3f2006d820455876c460b7a2777e5695d9464b3d44806f4e1922451941fe00c98bcf9ddfa4c3bebd24fd712f1605b5030a8e50038957547e9525afac6516d5ce8ac8fc43ca713492c242b34487125ec6a183ad0eacdc7f8764a63bb53d5d7f9289df2cf53d85a9955a09e63fd00c8e6a53010ac129144e213aa9a90dff60897a0c1853045cdc0d35fc245d8741274bfeeee21e10239b01aedef8c5ad1229997e577a41ec5d1ba136ebcb851106190aafb8c5c884d3629ab7c4d3b7d94f9a4bc49039fc6453e482d879002a2c68d63a3786d8488863f28627bdf8dc7f94438ced2862036fb4946016ba5991f9fb637cc5eb93bc4edb0b3456fc076b0dac4425d72d81b536495bc1942fa0d5ef8ad87150357f6342cf20b637daafd67cbab70fbc5c0645c07c760a51201d0bff0f5677facd9e38baf731e92d1e4139efaf9e193c0c96eae5f7d4e329feacdc1b85e3361de327c66a6cfb55d29c219c4422917f52aceee77128f2e3db943d267db7afaa703c625ed68f64b0ad70422cdd05ca18fbee677420d03bd8dd429667ab88456c4ce893942ba1eb7f6c2b3481f7f9c0b02cca8f8a2bdd3658f5e148d2387ae2bc726fcbd1e2c0af716936a1f3207a12a87ebd30130455e9ecebed372c1a9a57070ab3bf668569d3225c504e411de210b07f796a1f0f8817b69898ba5bef2a0c2c83cc5636206c2b7e9481a56a28a5df1fa39c3d9f4ace8707a064cd5d8cab52df075a421b15f6a792d539ba6a61157a1b717582d97d18598a1fa10859043d9823f41a43535c8aba40d1aeb2f14087cfbc2aa7f32905f2556a6373eaae4bbf0d41949a12a4b289fcfc0377d5f1b673844406fcac7664d31b8fd46b37aac19c63d4997473c32a6b1aeb8956c4e9d104d9a8a2c6408cb91685e240f49eab5649dcf463e5992e8230246672fe1e49583f61e9bc2754b49a9b04dd85d55d1e701636784235c69e52e08ec21cc2111473c9ef0ed48a7a0e6619e708d1cd08ae50a78982426ada6d87a7f69355921b4f9ddac47072af633e07803e25c62d1d40ef2a31a4ad036088231a600f5d2a4c72335b3405bc7337334ad835ce9095d6b7b2abb6b3fe275dcd950facdf69050e6fbbc329a492124a8a5030d8805170726ee7e18c670d74e48a01a8b0fc982b0e36b1fb2d145ded1b369486ff2c1a37e457f67cb4c5b085abf55f1e3250fccb8a0746422d9cf264649f7e075aa2e12e7a030adfbcf53dcc7409103a412e81f7657f4e5687556933f2f26fb75e16762d335e7a6eb39f05a2088f22d1b37b0fbd30a801d99fad466b20d46a02a8fc96cf44e83528aa8f0754508154c61fa1d46f48fdf03189194c9424c6853bacdbbb2d0f022ac481cac7b6089edb3bb090e6dc6fe3fca6a2cf8f4143d35433d5606ab31423ababf9382e3bc1a0e48e35c980cbba01c26cdacb0b4a21c403e8823fda69ccfd8e23d5eedf7a4784b6de596c54ed57ca66a32632c089c1f786dd4a6a2e9aa8e63a6ed4c8dcedfb273d77e796c352119962fed7f335fe572817fe8ceb4e5cc0dbc2b32f4521a58a3cc79fc970fa968b81883f138015b94796b261a079ee744d20bb571c78069c66c62266091f4068f41c0d8d09c673f74c305c414bf7ec2f4f0a0db6708577ba21d21960a0780593fccd119a33b35c80bef5a0709e05829bc9bad2b375d6f2b55164f680456005ef306b60a4fb9ad1124e4a98863ecf46ab6a3cee6a253b19c4befb995ea862a58ba98a49ae8f1a7235bc33082be8cef678b4581d3a29a902eae55dcda8c001580cd778d27d05f3df5601fb7bc8b71a4d58013b9ed810aa1a771709922c4f8ca4e378459758bda4098329aa5f788c0e65838d5f4d79cae63f1153c5841803b1a6d0aca9c67c14662c57111dcf3643d7b08790678dc0739384ab87e2c452f201d0167cf57fca7e46601c6c438473be3b514f91ead889f201bc722caf09a5c7a5d91a45dfc627d75394c5fdd5561606a0a0c844df25123be54ebb13d8e09532e207dad0298e60d89b5dd2ca6b4afbb56897c8ebed48d0322da01c91625155e7ce245486b9486666e285043bb12ac30a3ac9b4dd2435dd5203ce988a603f9b7284580694064405b2ecb0898fcbcafb78a5e89d5713a7fc958efaa7d369792ed0f1f007954e00e4d8d212e2f35e4a7911e2b256f5e357e842b452f55729a7792cac00cce11e5beb573c887a79c228b6015ac1de5e974a7d9c5238d9eb2c4e114d8c4ddc429a8c039ac8dc8d0fb4d85dd90b2be7a2cf6951216e2cb7888014ce60c96ba144a207614fec292a9b8fdbbcabe44268f8ffa3ae18a7017216cb8ee7b2e5d44733f669b120df1cf33bd7a4cad4e99c16bd560967a571a6a8f96085045464bd60004339b3df41b3af873e6f31ee8da996b5128b20b2d8a9eca19e6cef3ac5af032e3ae880f8f5eea4fa652612836e5f0f0f9435541d50db632ee585b5e291eb0f24aeb4b140322d9628f49e816827e7f08db4054d0df241724d0c1b42c5ba42aac92d3704591b4c8a4dd060e9a1047eaa8909686acf0b73932471ad39767f3bb6877f1444811b1e24f14dd28008f20b3548c605805a1b76c922e55f6fb10142d21a962a64ca39e8f964f8b99b0193e8be9840da5126355d02c5f2eff206e300c9ab5e912647217b91bff4131aa3c4f2c7d29481daedc9b7f2b6af477c1513e9c7f2bf08202d815ddcf13f50b107904d4be7280d24369faa2644d539749122fecca7d0135b1a4b3aa9ed44e7c88d471bc16897ace28b03b009bf18d3413cbef7547c8a4d5a28f4a4fb976ab5a515083221d4a79fc053aa81a74a3fcdaaefb7d4dc6be4afdcb33b1fda5b27fb0fbaec2ece5111d9f0cdca5a4b61f32bfeb1bb6e6b9e3ef0bde9c6a1629992210e88bd39af33285c9b36185ab1213b038918d6a2e0013a73c41d5aa0c6730dbe22f2ef80c38841ec49c661d8fd42bf0a3f8c0344d90c32a19e1f8dedc624aef1980c6683d41699dfc2404314800064dfdbc05265e7707f0cfe1e4abd774dc4a6d8048703281ebeefbe3aa620c020ef525b9b77c216f1e5a97a60a8050fca46133e4dcba3e9a78b566dc6d3c6e9a4987e7f2132aa21f1c93abf2ca3f84de39ac3eb93240d3389c02256c3f8077613b64b64a74d52bbf39998cb10e5f8983249f6339b6aeced88af88dc09f261ef6a8c6ec112553372e4e95cb360a76b29564e0d881c73900e34884dea41c56e5ba3fa8ecf0b93a48ec643607cdc8f7581781268c0a03949bc3782ccd72d41b3226b6477e5c3a1e9cd9b1876e0be523806c76c1e0387cbd75f4a1b0a1bd1a2e05e97a96b8d9e8a7558aa086ec255f281d1ff0527ec5f6525d5154691229d73282a4831b13348694a7d83d504f9b96b0647a62a209d13b4eb43010b989d2a43add2bc37c3b1d302f9439b25ff840f6b0894bb9a214ce2ce68cb18af7d4f580f30f1e1eb914d87fafc2dfd042cf59868e998078e804f923930b6e9bd8f402cde56a20e6f617f6122a00a551809d0ae3fbc3aeebf7b1579b797feda26eed5d3b715f7bd497e7af7327832fe13928b40f1456434ed8eba236e7ccd3b5ef3180746e9293960bca8888f8e0d7063f25602d2160a69c691ee1b0e936133546d42fd72f1e906b6244e9b550942ca607af4aa5a6eafad1ea2ab8b220de1bc6ae8c159ae97661932b6d4d9cc1bbb31c2f2d01ccab6593a90f2d619f19832cd940fb789336f373a6d9a9b4998ae365d24939d4e23f3a80eef45fe217354a4af6ea773f204697775a172d929f7e6c88ddb157d6111a7efc2879a5df3caf8bc913d4f90333b11e7013edc3fa75bc34fbc474afd58efc0ff277cc9991ac50a075670e706da83d0c17fd741a21441342625a53f81bfb6118b84010e0c71b76455e1498acd9d03125e2e9366b47273e1d4376a96cc9727f35245809f5c281d5d89fb0022b21229aa5bd01f02725cc668d531ef8d071f276613440359b00cb354618f3325cf303b53a06ac17e3d560393177b741db46a0cef3243cc4aaaae4e8761e5bd5a77289f53d5033adcc33f904a0735bf841a29ec5ee34f782f38c080b196a5f40c5f3c559440734db4ffcee676ec6884b6ee1194b029109d889e55bc7528fee30a8bdcdeea1a67d506ac0350cc417b124bd3f3491c00a5915bfe30ab7df387117a661943b0edb0097691552fc5b7017433b5cef496f3a01c2ccb75a3661418090e9c762c29ec25745d8e0995399dd62da6d2311315589d0055be2522e0f73464eedad10cac0bf415a4b67e5c79ce0308cb113c324fd429b1a1061f69d0e963a682ceb3b4ecab1f23bafb444dbbb92331d475eebc40ebd926b3b10727b01108f6eaf11e62b20ba4f5d35600813df16629c08d76e5573ee34d82362120dacfae281d4b64e2932af54f44abe11e80dffc49dd85ba7bbee967e3858c47d35c6abef32db2391f3a1a57afd282753fe792a26bb806c23de21a03a074447741adbbee4da1960085b6eb963fa847540c54c939e3739c64a2688f367c154de1db816f6d4c447c2751008e58c6d690d57e00307f81f5a60d7c890ab9ed7943b8d54806dc09cf93004b5702fc3ceeb4fd6bc66b2100dc508c1a3631c0da5696767438108974987f3dddb1febb61a0c4c9ce3e013cd61980a03b375f0772bdb4dda36b1e237dd43ec0d53cd6784fb71c4571efcc561d089bf45c13d8ac156a16bbf662efd3ef3b140db71cb4e6ae755a698129dd1bd588d7f8a61a31d1d81b26fa13f5f034a50b9c51453c416346ae7dfbfe5e4914291cedb2b8143464d15edff5558d0d19fa29dca01940ac0134e4a07be2a59eeff71e196587c8180351bcace52c869ef55f5c4f19d30e5e7c75019c03bb77120ebe85d25d9acd0c68b59d21c291922880972e2fec9145f39b030397c2b7eceb541f870a84b5cea6b99019e8fb5a8ddf1a59074c7b22db88bdacec31ad3308c8e9806b0a551048f171a1ffd4eb98ddff97091b5c1a33a1fcac9e5e8970b777e7eb405369979d7ae39474e02505a0a9b0e396d414815f530b695c84fe0794fa02a0535a8e9e2c0c528721c69af9c1166dc588942e362e51ed2df4639e4fa505105eb815ffaca039611f8676a4a2c578aef30dc9f7cf569f2bbf05527cce62800854102b5a90546124a1f7f36b723c6dddbe81106399020d00d3eeb777040a7423ba22d509f50d83678920ff1663a6ddbe772da78787a608d73556459c9dc3b1b0bac2b27a62346d410465ff33bfe12c96e866de1a49fb8b22f1000b14e239069ce6eb932b1098caaa81b4ebd20aef222f96a5775b520c64540662553eb01b2ec8508bc595c764f98ce24fe47280f892c87bd0924377715a986b802fe4288b62522ed05e5724f462315f3e50712e64a88bc5caddfd2314b99612faa4f0129ddb9e88210545782eb1ed59b39a48b663222ad6b6a13255c599d0b046949c83d2e78f42442b89d6e3ad96f7108c1957105e09f7fba66246f4b06550214491dd72f07de800502108014b1e725d3751cde44a96082b300e6fa4e80be2533ef0cae08630232b8d7308e64bb7c252273d2334aaaa1df6d79f54417f3c71401c44be8368247cbdd3da177816b67da9883bc140469509e3ba24b77bd768b25fcd4cc9b499c09ae1719b8de861a3851d8227a1d2b421be73ef395cf458b5fae778a699f7cc759d27f76c3db1ca0af36676cfeff164802757664beb510b2589fef01b4c2b5610a5705488ed015b014840e71fed713ce8b3b3c133c14c4f5d80ed27d28b6655af7fa75a82a437859d722de6eeea48340bcb50c644de4291f23399bf284aca05da6b479ae27612f20178db75e94e6808ea464c05fb04616293dd8620fa50b374626355119cef9f41e6fd262d1398a841fcf16f2b4fd6586284c6d43e2d770f0d06b56ed8ac25c44083035b004bb605bd8f4bd434adb81e7407640e3866b3521553400fb597441590af72229026020aa00d6d968169af71c74a705a52c975cc4702057218c25678baa96e57c8604fcdf03a16c0fe7b1af35e0aa7fdf953b185065ff2dd483ed02f2a4ce50d07e2ee3d78f664a87f226270ae2bdba58148f318524d02ae26b3ac29ebded7263e08d798f53a92e7c544b083795e1d01b1cfef6389668fe2e745032dfaf808561f597d10c81b44d6e36ea67fd4bc691864fbdd725847c1221e48dcf7a6296cff6b2d2c3e91043f923b275095660a13d3220e3262dd423bcc1a4d6d831ee197ac81ad78593e92cb99758eb69218292a0530f5661999feb700c6a1de823160fbd8c868da7e0e282d23078f9e5462510b8df33482c021b03b9dd09023244b83b081dd3a4eb9beb9f0224c432b0d916cb3c889169a65e3a1e6a08bb8c03b47fbffbcc7803e87e91cd3fe063d8b5edeea9c9d863344e2dd8b3490d4bbbc848ff0aeeebd399c51430277e81c8c7f0763f41b9bb7a19ca32a3f9e72775325059cf6ae29d4216f8839e71465fe959d185be690cfe1409ac0d08032c41221073a66aa0c9e774088d3eb6a4dcc1193b07bf974f3e94ad832aa284364099b8d700795f521f7775b349bcee103d2cc9a357ca0ca2927f1d3b3ab1a961af76fd76fea3650bb8942e987a2d350deb3d529cbb285df769289be813f83078490d77ecd7b65ee769cbcc2c741f094db7fd5509d2ba268c0c88d8360af038fae9f74b7a1a0e2c36aeaaed3c373866030ec00593dc81bc8826c54007c2dbc69646b1d4f9fdc64d26765a4f413cbebe097e2b205d44611afc40171c8e64005785fcaba3ab0037243e88da200c58ef5befd7b3f4d4ce5ad0cec068a10a993d61b455d71b04d833d0b6d47fde4f8bd1dfa548e796f148b52d15ffca7c5bed40f9a0b5ac6aaa1ebfcc8ed3fb0bd771d627f96a93ea478d77c4019fe1932e82ff90f1ead1dddfce1f1fab898497ae2a46f44b776e6c0e06d85137506eb0e1569025f8d796c74a488ddf76c8fcc4c7d128cbb326c1bd9c59a0fd25944c42fecaebc91a625508cb75c1753357f9bd6c032efd5533506b136f0252493382c7422f6f30177c4fd8a5a7208259b365a7f40b8b52187e60f84f2f38ae4719a0174a7d6cb4fd944c32fa9b90b94053650e89ff6146ed401564b2b6535c1298c77ddaacd5ec28c4e55b3cdae363852111e3f0c4551f2298a4a1d5ea98502bde80d6664588d40666e031d34a3559881abba8668616f8a0708e7b98c18b371d637da5f112c391c6c75013e431862c71ddb6848a19da6c8d05c136c955eb799aa9f60aafad1df1cc16b7020e667672446714ecbcab352802e31c6f9d8f15fb51f5c12c7283d851964cb07305d6d1a6268fc537bfc7702e0d7f9c5294541baf6aa4c4b1ef91b01b3e782384db6470fbb2cc2848ef96b6213727ebe8d2f8443ca908852d17cd8e391734c1b7e8b5ed9dfaf8442f94c865db319704a2e2b1701370f020aa4ec4d422dc3c4f65ef3fd87918aeb339aa85e52c6d562d234a84aa1602965538342a0795611d1398c08e194d2f9baaa83ab97e43cad5be9902c38a1d9bb5c215ca8a0108cd31750dd166878c1b11e156e544e6e5c324f17049a109091a1717d83f67e224253e49a4ba5ef66f761dcf2433fb942bb65a28c877cec96054e8323ef8e7d99abe7f9e0f08dcae39f73569478087e7d3b3a6d32639789d501c626751b5f6478283edefd736f0bb46b31e2c31a9d5ff7b217580289b77171bca7b518d0f005823eb4d9907d2d499e843ffceea50332491250005274d74fd767f217a9dc4807317fa26f25f5d45bcfd41be4f420523d18697ce665578f3bdee2e4e84b81f1461a6885fa6087ca5f8b8720bf7e044c36a7ccd522750b5e05e1fc98fa830aa2bb48247c91cc345b45146f1bc11705a8c1a5c0cca6ead9c7eebd840bb6678bdbbfc18e81865211e0976f2a189e427650316d712d83f14f04de1ba9b582df285e75e6a257c12ffdaa7a48a27c4cca8c58d21604c48f08aab3cf547468bf088778f4e75bb151969075d2894f9d334c12924d690b8c231c01ac55749c964036a5ee1024d34d9e927eaec4b7404f91dcf5b7f039376307cc2d33265027e9256a95ead74449fc7a00d46c2c8d716fcb5cb3887b0bc78311b3fbca0c08537a774517902891c190480416f2ee9207af68af013746ba64cc45e85d346eafb30e1e37b1074202470f09dc792a9d86685a5dee7e2743325ded5a2f186f65b371974053579479fb5acbab2b2ac496c6e44b3c237a0a47fad7e22545b6b1d3153acca11ada2f0413461626e2c75f979151d04d3361367b1e77615be0c5996a9bad8432b3207c2a3d5b8a212ac4ddb6325b80bb9573ce5957ae17c496b0052df28bf9c30fdfaf385cbe22d19ef2d9d114d3c2f7626429634cd0a7d1700b95a2afd43d0c676ccdf8a74d80a5115e88a7215ce321776cf2cecd92c33a0862d178d3a2decdd7c9c3ab71db714df5034fbbe9a87e8cca6e76ef33ed63676ecfcd17420a01d767cdd5c3936bcb2e34990bd12d053657f289e67d6b4de80714418a9144592240d5be78016ddafca4aefdb594ac2ee81ffd18f018b9c23652c99748341c90a6395e64d07a01fe3747ca2a6656b094891c00c2071b5efb609d39e0947a7c687ca5f6d3af0c2ff71477912724b38616ba099cc9344b96b15d5578c462077b2fa782455b0afb0dd70eebfa39e1d9b113a2f82cade5120c6d81d80a34164befe1675b713fc41a017c2051545058823b381d4dcc6cbeb9a7ca38800fe15799fdd7f827448697c3dde934fe4d245c87b47fb907c0e7a715dc16313436fe1ed256bfe713322be83b7dd508cd0c34b4bb275b84b9086c2e5700f9957d0f5b90cef4df1fa597232d6016a565805f1c6d6573f556dd4997b00d53fcd09fc633b8d1654d1d579a7bce1304867dc08e9fad6ec932e650cae6b3df0290bb1bb2d2a4419f9d7d0c3e9391a31ebd0113c89d0e91fbe917aea120455b47fa95bb4e1beee089d7ad68a15fee9c53406e40097436d93206813ee60f414bf0e04444c391cbb8133097bdb9487c55803535eb2438d6d8066ac388a45655c5b5c84e9ddcc5a8b23df5010ba36cfed4e5fb8b8c2ccf6c9d3f89079fc6ee29785cb5e774884d315abae0a690928537755fb2b4a1fe166ea35c8aab8c3595da9c1e619288f85cd52eaddf932e4fb25f7d716e2abec13cce0d2dce7b92eb7963b70f5e460e3acad36eeed193ef737ede13a235d5c580da89801cf59ed2076b2b35a07dded0a7d2884c77dd3e09e31e3643a942ea31ec5d5b39f2daa8b321106b5ad2fdc73fe775919669cca4c5268e96b452d9990dce6381bc2f86dfff32073ec08597e13f5c5cf58930f105900433e2881d41339c537b0bd422504e87a3b2241f9db0280a9bb4e205842018472fdc236b746279be88700bf73a793526032cfc73cdae4aab65e6f750242a33a208931dbe45c56c8e4c0c6f4224ad25ce45685675fc06d0c550f64822e1601d1c400b9e961a48fbeb70bd5cb363857ce3a91708104c0f9c5bf93043f789c04f0e941c537822ae6acdc550ac78e5c14ce00a95e8c4d746be505875ac9377fe53d6ab8e21d76ac703c6098779f7eec0f658e21da8b0e07b338ce61ac88aabbeaf5499139932498a25a1a5c2e717dc74622a2f2d309033458a95b748ea5bcc62306002684701b4f69d4007c0c09813dc13219e0cf09070adb3d4e4469ebb17c5877195bac28f143d073687d4687e17216ca1a553587a81443a4068bd17925a9beb2bf28ddfc66c01bb439a99bd2fec4f0119c7c2bece0381afae1ec0ab6a48d7338bbce1f95b2ead7a4a91892433a69b197e3bcf9202822774bdbce05493c83b300ebc74881cd5e547fb6dfead01d9e285104d3fe8857dd228f0aaa0b545b8b1220d58e1310a399ed479e29e3c0d165334a21a0d14b848af35ff60bf2aec8c9e2d03cf0efc435d53b6ca141aa7e0e82728710695168b1b219ced6694cc7346b91d33de3a2a279c3d218ebae2f955250a8891b0094d2a2e95d4dc436a6280f651d1bd2ab401f150c5d0a5202224f82f5fe5773f24e04a3093a86c1099ae943d98e77e684160a824fb1afab1d01712a2f56dd2b80ce5999eedca1c80be00941c0d9c07fa2850c7d3abdd85dc827618559c4a560cf23e0a1ef631745b892e8a66dc766371556bb24df30027f5726908bd7f29dff14086f6111a7fda590c4686dbf32c3d251dedd30aa4ec1d6ec472735d2d3283291b492ac9dc9f7d321a7456ec73eba97f30c7e3d40d5356bbf390cabf1fbb0a6d8d24a3534228f68a20718a3f4b03621b5ced8b2560c1f694c77a6c89f5c93cd32abae379c69b13ae7ceea6cbe01ae32fbae6f8344ce8119fc4a4ce63afc3e04431b42867b529cc2f608fbfaa8df442600dd1c3d14c2837111bce6fefcb500fcf1638bf99a90849c4324a2d1d879605db55a659573502c6b6c4db689772ecdc0496a614cb81e437af40b6cc356c3292bfcbbf38b97c36a69e901feff9d631b7ac8dca7003784a09489bc566c95b42e42143ef2ce8ad0cfa38ec02f2915a6226e34ffa408547263c9868d12b089627342b2dc5f606b11da1a5ea4ac4c1bff21b428c150b58b0c522162c63c5022b6cb18805cb58b1c1024b2c62c11a6b6cb0502c16c44a251b84861db6d862873596c4e26b1e33528543f1d528b48f79e211c54aa59d173f6aae72a99ddf479887a6523874ad992597c2317aa14728dd77a2a566c81251930fa5924eef2d88d2323915db8d4b5c62e9041b13b2a45c4d1c02dd00e2e5f0a72d5cb25bc25823805369a4f81fa6a1971499669f96b072d602bf6cc9b5261f91ae94e0e71f724b5688faf111742921f6a168494af231adb492c1c71ce596aaadd56347d14a0a988f4525446919bf6ead40fb7737a99e002e21943ed097ac0e1fb873e03d3101548d02ca91e9515b3052b2d21ca92a89a8a9b2ea4d808ee6c7e2a69da29da0ab6ac72a9324db4464156634bf77d39e28d785b60dcefa033084c121ce518f232b031cfd1edabcc594e58d04afe4817edcfd71104ac55c6c7e1f3276131112da44ca94520a050205150548760acff3bc25f67f4ae93c1fb96772cbbf2ede77ef3ab0a52882eebdf726e0852386514a4d3f3a70e4b11173b05da823688e4176df7553abe126118c1ca92e4b0000828bd20e5e21b46000971fa43ba6ba64bbef4c201147b9a88210be31c13c6343fd850733a620878c603c04419e7ec8888c11640e2b08d2f430e010083220878c3415f214f2f808fe461a08d2549f6fa53e1ce5208830823ae6f75f1b4eec977d47db28ff3aadfdc031a788902113e471d17f8c43ba136a68b8eca2e7750f0d6ce0d45bfd120b1bd04fe97c1e1775ccb00163ce490b2b5444c890faaebd67efeefea5388e9e8b47ee16ee10d22d7adc3966c058d53221b3cba418eddc4b52da025379665a7da6946b1010100c634f29b1da2dfbe5c4f5e2e9b6f6c7e59af81ac92ddb49872cebef2f58eccb464245c03d1c0722bee348bc6e3c4a4eba7e69e84bfac5371d06a3fc4f2c6bfaf2d3b24c63ade7a9526ea31f057be33aef746ac1039b6f40916f80f00d50908bddf4f532b5e94aa5356e3f3feb64dfaf310614c4e707e88797c03d9871a8bf0ff76cd79f04ee6920a62c5ac845f614d4408d666a325becfbb89874272e6cfb15d83f06320996a89f3b261bd77f7a38d71d039db451c44829d1c48b890c4837438c2294d8410923295d902150da624916465574c0c2e4ca1248bcbcb0422504b9c96cb14fabe85c57302cd3dec6457f0c2bebe8221ab6b471f71f9d615896fd9817cbb09b7a46e74dd0de21ac0c1cb0b00169fa0e1cd29aac80f44ca0114f620c2fc8ee3dd048025c20bb9f540af6365216fbecbfd0b88de384ec1855a98112b769d86b3c2720293d000b734617f9627d6db4000ae2e3ffc3cc3c328dc1be64a4d20663b2a3b3fa0102e5fa0e124e4905a21ad79f8852eafa0b15a91966e33a9ccdbd0ff06e87b55e0f6c72ee309333cbdae39bfea4cf6773ae0530fdcf3ab3c71ca4e8e006561225d020dd892b50ba3863062f88818474f7a798db303643c73689652b481021b1ea603acd4d11d8be863ab89e5af033f26c618ab32891cd3dfdac858b162c16183ac08bdc12450b97d175d8ca1627b76996699bdb074111414c61c2fae1872548e7c288e845449627be2421fd471ed75aebd6b271dc6ee8b883e980740f7afd85784e9e93c6d6c628c56837fda4e96561a98b5ae536223f2638a07bf473bf716995d075d4c52afeb45a3fadd68f8beb6d0a6f4c7220931ce8e4060e999090465889dcda072e5ed0813442b31083dc4229f38db0decd437582025bfe3701d87f07f7cceb37235c4fc00d3dcb440a85284a122a2622b20cc3b06e6acab22ccb3ec6e2ed842dc75ead344debd5aa57bdead518e43a7347688e208490c3e33ad50bc75151e8c52fa1b933ce39bec6711cc7d1a8c3623fae3cb3c4fa330e2e00fbd94a29cdb00ccb28f5db3d69a536ac695a054f57268b2da206de19e230533e1c36666905ae9b58f2128b252f75fd252e7644dd3d3febeeeeeeec352661b35cf46fd393fe61b2cb61e306cbfdccee7088f5626738a43613efa43aa4571345d44c9c9c68608588b8e831554c2694cce5665f7be5621385bd24868ae9fd60bfb28a564458fbadbde21baf8151fea500aed718c2faf4252e8a8e695ad5b4aa69a24daf7a359385f83dcddbf81bdf0a984980eac9992f09e0433592335ff2707d20934cde0099ecdcb3f1333642fa47481bd67e609337c0182af97d834f6e3c0d38a4e5a08c29240e55256ffc0722a1f9ef718043381e0768c49d7041829452f6d56e805e047a131ec19f865984d281b8cc205837bafe1df60173f6cf9e4eaa5125e2fa1645368254f770135419ac0db7cc74b8fe335f6cdf14f7f42c9aa8721e71fd59c06ed9aa79cb2c858de13f5c16b05b66a923907098a5922c0db13330ae899d5f6263504ab314dfb80bd92a234113dbb7cc52591ab69b76d38e7a8e65a94c952199cf5aecfc6e3133a5592a4b55a3cdc5bc25c8517e055f11a483d5c18de38de3bf74bac2e868b5b4b4b4d4aa9522d56da889b0c20c33482373061dc8fa5bcdaebf0d17faa33d6787401de8d77efe68590a3392aef6fdda6b3e6260fb6a8fc2ccb2ac9ee057fb4ffb494f59826459a669956b9b702800161b37706519e089ceb2c694a0b1b527f364c67230ac874b8c252ca7090fac7372b44c889cd68f862406ab1d1cdd50bdc0e8c973f29c18a5185d8151fe1de4a2077a0891eee1489a7fd01dc310bec9c2d2c7ba1bfb12e66e59b61d19b202e64f87a42044052122587c44805f302e5d25ae3377c6212b9444845c677ba446328e7367c8d2b8340e595a1ac7919786bc3c2eccc35a5a7262b2c4e7e34b72842a8a1440415c2e97cb05a4b45d09a1b36ee3e2139613798c85f9d3cf13c8648c5f4ac1264f600c958cf91846f2f4322f03aec023c480cc2294dce53208be430ffc017e828293a7b7311f130e29c00464287516e4d11164c33a51f361401d41f661de863e30739e42a08952b90ddd83fbf975a2fc85ebdf62740fadc35c86aaea5a5da72ca31ad82ab06568d1042dfab70d8df2c75a8725faa10c8c6213886ce0917b744f3f07effc5beeb13d7d19d6a94fff06eb6825d0102ec88dd3fdcf1d202c2c4f59d82a1bd11be748d72a954a3577faf56ad5ebf59a3735c755a93c3ffdc0dd2d5e7abb395bf3887b4c4c704f13dcf383ee010269e9228cf2ffc8b03be8da38682f4716063d83ebdfcd60cb3e62c275e6b77f877d74ba366cb036517c4022dde90eee71ad8942a23e4aa205057684c4848b5e4e3ed8b28f7e5c649e199665187d6bbfefff8f8eba2cfc9b94d299837ee6b9896bcc094fb0f8ce9086dea66aa5789ee7bdac1934b6ba46e09980cf799e93e6502a02d20da8d01c1e1f48767696733d27d386c8c971b9ae5c7f4a35ad8f60947f1354b096e9fa37153e037717ee2ab8741eae3b0e9fc360d1e81e985ade979da5fbb2b9b8fe4ce5084b858abec21bcbf533ae3b92cab2429336ab3691292cab176a5d587e215d7d3a258c36db0549567c53f4b1d52222de7e0dd4315f7b2ad454b45c4d6c91165ce466b025bb302ce36c1c473a0269b55c302910b2b2507a55692c945ea92064888fe0abee4155b825bffedfa751fe4ad5e7557051e8a52464eed821422efabf58452ba279820f037e20311292017e003cfebf57ab5ef5aa5740808258271d6af00a230582125ba8bb63d8cc18a6caa8c04eeb030cd40c747fedc282517219d7bf83c1969cf4415c87be3f06b887863ebaeb4c1098e10f092efaec7cdcf3f90132b1a794324ca53428061912cbfd87837bb0aff910be4ffcbeeffbbe2f887b6df202db402765b2585b637030c8a8bb35a0af56ab86d51b90fa094a29cd786064c462b96a522a85e445c73bece83212a1b411c675b5d62ceb9e0387ba0b6b861e4823130552c82e94b27dc7158a2ddba8a7b052124feccf9d9ed248384988432e1671d18dba478da1efed29cc4940e182eb5f7bb065ab6e0f1969ae69b355592db2257bd99e3e0706690ee446758855ba7d6f141c72b1555ba8f2d2b7c8dcb12915521bf55100a0d80d04b900678075680e92b68d63cee7297b1fe7d1341e8d47ab13ec3be79c9a91513027d0048a4e7a1056cb0f9a0a1955ec200a1ccc39676be19ea6405c742017667bcad2f7f11926b6ef13d593dd3fe9422047b596ebdc4f79ba5d356e65269ca0799a66e2bebb01dcb671dcf6a3c7372ed26ae29b9fcd4d585634641560943f8f205ad85148885f4a5e459b204949419292c614b00034cdaf937588996575c5fa4b0d068dada00f8dc5b0253b330ccb22d055aa608f6159773767180df2ca6ad071073336583f4ab339338cfe5743770371207c33bbd829ce3927cf3aedfce6cfd0d988081a5b7588281e2b5e5a80cb33c725b6bfa938dab4a6b58d981519115161d825c432e221c447613107614b767d73a79b18f110e2a3b05a1062dd9a84d584ab10dc53b89ec0967cc67db9805c2ed738c467bc8478480a169f6174d4ae0b00498002ae1bb8c6711c6d4616cba8ca8d57172a01d4e0ba82867d01a6b1227307018cf29f9d8499d22cd6052ec23d5b83b19a241002863fb1250f4f0acdc353c48b172f5e8a68a971891d3a1dcd7478e15038846d41c680813a823c0e876a24b9a207e490111318a414ed8d340c4cc82e94a2892e3ee96e7f7bc13b216094bfc5a256d12e84a12ad281427c048bf55247e15fabb05864e9c22013b5d2999f10df65ee268d82eb5d425b9ead6ba4cdd4d9aca62558a7ab34142cfab78bbb65233d801ba986cf676a600bd8e5421df39b68cb7ab50469e54a42aa5ebd72756a8dd448271898351e4dc3220c931300ac5eab1915d06095032ac6256ae52dee45355e534329ad29c7711c475ee2799ec7c57e917ab972905a465caa8436ac68286335c69cacd50e4c69534ae916aa22f32b90bf2bfcbeef85581f47f5ab543c3ebe4fc98a86ff39e9cca1f48bd1184a7278c2dbff53ebfc1f46f977a88a816fb629ba58e18089084c1415714fdd727df6f3e5c026e7063249c45a6ab988584546474846472ea416ab68eef05cbace64b75c4a29f1b33729e54b4546454545ad554a6b171551252ae36d1bb1bb7b3e9df9408334a473fa173c565f38d98af02db3e3baaee39e72dca8715c2a0bcf1dd3572e6c9723aee8336612f770dc23734746f9fb911b4d2faa2abb22ea2346e26e718c17762ba1bb2017813a95c437bee34a1c05de692d18e53e441104cb4e72959de4e2240deca5e6e2a212967a4b576d61cb5e25ade69c6162998bd2d8585c4c9ae101282808c6e556ea7da7795ef7682d68d083edbedc38d9db6967d6755ed7795ef75e67cae1f1d1499da46493d07c1fcd170e7d8f23643ec15c7ffbdddeb7f75d1035646cccd49059ad56dc7baf851fa843e673fc119206d4c13d8e3f42767d63ae3fc3c0b80be699b76d46060cfa3e473894e3bfe7c0209ac7110ee1789aef72e0d011a4e373bc8e1cdce3007504e5781c9f0307c7cd703136666ac86ca6126edce0ba991a32366cccd490a9211313c3bdc695393ef3c3de827c694026614016b05bcea2d28f260dc7715c97655db771d486df5082d5038dad9956dba866d6d4a87a06eee1a371bcbeff90ef00cd57008f9a3030ac569a24ac115063c138f44b989a5bc3be6208cc2d7989fb1a1e35590e0eada19956dfc7bb0e886ffa37d16604d229f9b651a4e079f13fd5e21e916f9c691a2356e78ebbc0adf0244de338d09136d08f4c2838d952f2257fcda389345bd33593a61275d124faa24a7387c925da9a3b4cce967b99abb9f4229a2977777a94e4e219bdb0aeabaecb64f1216ce9adc71ebbdc6a62c46dd38f5619b63ef6a5b734da4d4d4cbde52dcc0877668d47d3aea4c06082e6057fc0a7ae5633ad42d19a63d2313333f35091148204f1e48cde9c73ce19ffbcd37dbfc83bdcf7f738797c4870f1c3b0cb26c7c2d2d0e392d8fe926aee537847b555d84a5c74b06bccc096adfa5e29e19c23c26768eeee9ac7e58ce9f263cc3d7ee7ad20e51b6c260dafb57a17aa6aef98d43d18582cac2eceefc1b646450c368021899409a0b830eae393be4f9fd3a74feff93d67bb4f3ab149e7a4afe34fa99534b8b8a5b517081da44812c556440b76a84058494307499eb8e50e17ce80624ef7769deeeeef1a1793a67ff36b66cfd44c8771d9f949a8dc72bcf3e7344262942589177509a52459688c48c2c5490e52a48c80810b4cca97ff187e0ace293a4679ce0945aa09f5f921c1e7c75a6b7f4860241f127e7c7ebc3a8e3c3e9c504272c1086bc5bc2c0512295b111e5eeee61dde857af3bbb1f9dd9d7dfdf5f7312a7becbb0c9b979a4ac07ac645f7ea9d7fe79cf30519dc8f5ee9987b78eb7ba49b02fff64b8a7d77007b5a590bb9ed2f7f65147f16623fe79cf3c39e520cd37a62d91333f69b21cb2dad972c78b0d9652b5fac38f2c41724296430020d1e8638d2440f43f4a0045fbe28b2c19dacbeb8ecf77fd9ca1722ae76d9ca97a25b8a2cf8666a320df32f57fc6de74006972b73d94a1a546e8ecb56c8504273987587c751b4a4b4e4b9f47974b0773ac3a8cf92ec5f1642b3a7a0fb50eba27b586cc9ad9c925d24b82a27278612dc72c7f5993b5f1def984f7b866bbe98296a7f2eb0d873e368356b1c7e2ca48dc3ef38740a50ea0113714a96f99428dc2393d5db4387e2f1d32dd2c96a966518a594ce2ccbb05954a4a45d7fabaa354a948e2ed18ddc272ab62998fdf4e6d44ca82edcecb38c65b3b036eda619fda898dc5d77fa12b8efc05e02d27b02999c3f7ad5c4ced057cf71ab1a5828c82e9792ab7059917002ebd42a5954e9becbeac6fd7ba6ae33f9b8d7512e2a6c2a405ac075fbfd8eee09f6147ca27a127b669d1a2cf2a358c7bae8fda2edf8f946673e7dd6e95cdc711d64d13f0bed44fdc096a35b8b346980c38d511726312958d1d0852d3be54d1b47f927bd169813a53027efb14ce3adce49b749e789fbce44a949a8f986dbaa9661d549579fa0b115fb56c9a0ea1ea7d3b541071fae7f67832db3562782fadb979dc2545898a9cab065a7b8a71f5b9aa872b62e0bf496b3e53ad82fcd9d1a0ed577d64dc1c5ae464470b26041eab693cb42a64ab2f5a852516151584f816e5160988a6f9c04443835882ad54a894a2995cab99ea9325545f264d0554a65b2d86ec23b9d9aa88d7b26f7f4d36024844f8a86d3e5a426e280058630c01ae0206274a1946a9aa6514a6bad400d6a52cf9356c320444e44048488a8fee04ac785c9f51f34326acfafcbfeca69cf812d60b7b6805ded69d52a51c73bca66f5b06d94d63a3b0e095bf24b48072dd01d208df2e72b5edea897125baf080402aca365546c992f21564d5ad13082cb064bba7bc290d9fcb45aad56eba74271cd6c5bd1cccc8c734f50110404a3af652fde02c36a75cc9bc9343a4f559c539ce214b9aedbf8a6352d36e2d778265c8a4d4a47c0eefef509071607677a99385e70aef3e717c78503e4402c2c350517471c0ecc4858b41d6cf7621d0161d30fcc1444fb8f5a4a77f860305e6f335e7f1f2b3c5872fd7f8030f7e13ad72f73c26e50b8bad5ee7b13c824f54026eb92a5dff5ec2d87521388c4f42f741aa47d0aea40c149af5de35e3092224ddf59d0f41dd8e4e9f4a770e8f41de76d601020cfe1f171d1b3207bdd7be150f6726206d2c8ec424aa97f8434bd07033ee90298b2a8340e797c58a2190000000003150000180c088482e19068402e9f26f30114800c5d7a366a5a3e178963b15010e4300a62300aa218000c00c018448c420a551d07d7c1a9b937e2bec48f571de1198299f8193ed174a91afd55bcf5c446b76b7557f9c559678982f6ca7965ca997c32a596f361b0369d1f77a698a07e6ba32d02240acd3e35580c219db05145547bc1edcca36443d03281855e8102ae54a9d184e4730781b53f97ec0e389fa556b5664d15e3c68902eb695e5f890995351ce9a51963fe011e87f9f3deb0ed15e38f906c6afcca2b6891c66e673f981d51038d41c48cac5c54bf6c39802f87e5d58b50f72a4957f0520f7ad21f48adc9e8ce3c75bff1fb8a0cea1691f1043c6b4d30eb97056881955af58298adf7373d5209e2cabd25ad8dece5f50ebf84059e0b98ae448a27534b0065b8baf01f436772b77ae345a29c084ba17bdee35bcd8fcc97c50d9549d09c63bd559dff771a82f631bfac5f66085494295d04ed6d7121547228f41904255b57bba46350b30109844cde60b67bce24a0390ed87c0c093cb4f193c8acf1cf95d4285bde12127155d2823ca79e8f1fbf4c2bc40ba73da0a2d8addc85d210ec976e71d68c2d86d8526e2ddea0a33d414d0b56b3c67d987a628dd91e5251fcca5b0654169bcb3634905c74cbbe38d3e68fe9da91b212fbc995bdac295cdc80a5ede9fdc5c4a825dfee1e3af83ec36602e138c70cd419b83b12f8ba43e865e618b482cea01b16892bd4c15be6c578e7635cde0032791bb8b66ad8531a602eba004da6c5a2985d2cb56f371bdde5dd2ec44604b0c90b61bb069ea4c44583e1c00097f9082ef6e6f200a49a1019c6299d31fd7f1f94339a10523ef1c0e8caf6dd9458f343ed6911ff700b4b78a9e5cc15072f89b78bce15677a27a23b47d69cfdce303fa4de7100361d19a01b9c290a02461fe0c14b9bade32e542cccbbfa60992dd1721917dbb9dad0dc60422ebb6edd4a855b1b4085d8efcf57a1b12ad1055e248b568991e6ba0d20850821e089bc38579e5095f6ef2dec6aa2be9b8730dfff012c075677f3b3d822e0b545166e8fe83b2cdc74089439a4004fb09011824e171b83b2a83572fa4f9b071db3febd9d483c04cd2214c507173052df9d5c32c0bee7421e82f8bb45e66cb4e0907a042a165be2cc97a3512e1deb4bcfda223d53a4971951f9c5274a8e97e82bc5bbf7ac211887398186ae2690e6bb00981b7dda8d277d474bdc46c56a441d7792b0db18c19843002e62b4e6fb715ce6377ed81283632723ddde029d260038281b54485c71c1a5e3623ec769aaa5e2f95531a48a58d6c506a2e2c712561ea1cc3d44f14392eb6c49c9d85b151392bb3429aefebdea6507417f1767a62b4ca6a290d59b46014943e35c4b9b6f781ab415f40178dcda0ab8f7c8aa08d8893ed4ac7be505da1d871fee116372247a94e5c675487d2bb5913f699db470c2443d380ead0b8229a3667e46772ee49147298d1ea9ce1bcde32b28c18f9e8bec85e7b8dd1f614605956d3b53833cd31fcad21ded69a2ceb7d8346a063129c5c85977abbbfabd37f55c13fb4df95ff19f17ce9157c19b1e9797cf3d70402ab423cbd2afd4af90c675da4783ce969732bbd5ee4bea56abb4e9667e960afb36672580a7f9e74ea45e0b14234c79123bd148809b83dc292812878f35a3a8a078a9e4c636d18277bd1b314a15766ae0c9dbaf828d02b51cfd635628211976707e4a05211583583489c1d041419d1eaf176614b33cc00ce156871f49a528f4480087f93f0bf94be94c80a982d10ff68b302f2486afb9245c8104bc010f07eb4825f228a8a47fa72d8145478a915d74c4b9316baf4704ab6d05244fdd859a35ef0896b2b5cf5a180d83efec256c6c495f477185f6a78325c54dd621a2d6a315414853331c6c5f2e9e1f2254f4bf2a57d86a5841ad2ac43a570178155e84c242a977da6384b3237e083928d4a84eb33f6c43f1236ca2ad18d5969ebcc46c53ef7a0704548dd13afda2263486ae0b596dbd999344c180d40b383ecd82b616864a5a2f6de0a1b72b2a89690e745b52ab48f65fa1a702a8ee10b24d9e8c8d152e2a52a62b86b8523febee3f9172eeeae6a3b56e7c7f78b29459878410700beae9ba5f984502d683188d2f91a231eb4ad8984ecec623adc162bd0baa3b1c069a2d65ecf713021ebb5532c0bd3ea17578af6f6f4aef6dbd8edff2481866008ed6340dbe2bab3170840e3d9bcf0dc08b8e822343632cfbc60525ef52e8bb28b1f5a9f59617012fc3b45f27f1605beb62e040fb769cc3cca5e0a71684bb221a74b8428287f82df51f0a926123eddbaa01a78b4aa0cc45cf37c9d7794ff62535543ac70877da1c1eb85013ae8b01f084048d3eed3ac6ccc1a5d8fcc477d0747b6d40ff99be070791ad296cac5e044a5837330120b13b97e08095ffc8b1a1d3396e2a1632a634106033706c1d52643b7ccb31d11c6e9ddbe07112eaf41ae804b887a339931969f469455a38be7151d0e0a8b0a451377f1a94b7416076353ba1ec9ef1734aaf02218410c48f077df1048ae001931ef8384a5d6b256e1c3bdb0f1b94a62ed199176065a324d8cb78b4aac4b66d5724bcde1c476f9463698c56a9923d0b4d6f81ef92e49d2bb266da472a06d5dc68ef6a135bdba2fc58defaabf8ce5520889681443fa046454fedeaeb1964ce0107f60d03a41b96fdeb5c0d5c827f95d0fdab3c6104295d7fa9dab8a30e50efbb1eb835cddb45221a06e63cc32478e0e7c97bdf4bee0a8fe11f99f14348c21f71397752424c4b562a8c3677d8490e62871dd730dff607b6c3a0cf6228142889e550dad0fa268cc955798cea75a9b079a98f649e7b53395347641fa42069bab735802ceb7f591a165982d788b5d18bf6ce0670334c360ff16c405cda41b5368a2b411dce784850c7652c249c8aa4d5d6d00a1b6202858238d70287f60d35bbce4e233846c1de242dd1231ae2420311047762fc81c778d960257556810c6a3c09f6d466bd50cdf9196195ecad0e24b4bc9ea3f8ccc4c909526c6c973acf059d7e72754e9c253131294482deea571af18fe1e4ec11e0b27cbcd0d6d47e86f1969c575868fa92aec9d5ec620fdcb1226b5171f191b1c29c478e2309d5a5eab3e95a99ad0021f203da245bb62210606fbb7e25799000d0d64df2048c12b928cc5e516e82d6310ec793e01773191bb311beae525b8de472e891f37d9a730ba5a4074fd4b944248ce1eedcdc899ddb0d35a95777118fd8b2ce0df49b0741030337e2d22305da5701fd6e160df115e8086b14daca5adbdd5c3b44da805a33042295a0245ab5478ee659180b099e50cd9efa2bd6898798dc7d4559c2158a21372c18bb272b606b45702dfb9d18e20ce5cf503749c2a7674bd7240dade9834de0ad8936aa066c3578670c5b1059715e8655bb479e0f55b0db423f4da1c746115782578f3bb58f9091370dc295891eb65806fd283056424d7ecdcfe2b0e8915ae83a637a2b37ab44702d5b4dd5c0896f065650b18dda3f3e45eda0eae34695f9b0722a22aee20f45eeb40a84941ad36a08fb1a2d3b0c5fd60c81a8b3ec78ac7a5e3ed911595624a5f15e7d9bad4bba67daa4814850f0c6be3a32f54c1cd772c293ec1621059f3f8c42130533ec754b31b47e7ec74d68871f949728ce5753934e846c505008dfdb5aea75021bf2d60523f51b69e1caabb84760689e726b61494dbbac1a45619040380b949a3eafdd921efd044af6e7c9618d88d4a66774faec96ea77f4591455ea4a85e49fda58e5d20863b0d9a8473fc75c2feeb98bdd81c490e44ed97393cd1cd0728e108da5953f42bd47e049c82af9bd3d5070804e4714fea3b4f8dbde54d189f7ce06b652a2af5aad704b68aee5038b3d1b1d48073a236961e2b97ff7735aa88a57d82d2397bc6a73254bcf22c3fe152a928e18869a6bbd034c2562b08a00d6efcef67a3fa397ed940a93357f6ad8ef8f3047c01755a0614ae91159d8966cda0c27baac78a8db9fef3f82443dc6bd1617b011894fd4303410ee9f38e6b004dc18b7a69a83b05362bd32410ed7bedfe194618c7431ec762100ed9baceb6811f9b635e8847f2198303e310b69055799f51d18c24f7836a2379391e4063ab04a9f4f645c6491ff7af854c105993cda855834e6f2fcef7e5a949395f3be978230413b2d846d7bef3aa59d8415f27070a43996c5e31ad08ed52b1eea0b3b8b2106529b018baff4521faaa6d6f0a3e6ef9a4e4af4f4997484b1595358220838fba4a13a5e2f10f59919ede893598f4b7660d4852988c977b8b471767a76ca51ad3464c42c1bc525abd8bf4e893599afda7afba138913d366367720043799b8751cf5ae722ccd6a75b337a4c37cbf1de8bea7f2f0f5988d3d3e222ef7473d103d3ff51c8a94418b807be29a247246e052efb265157baeb0ad05e6a7f638b90815a3b1d3ba924078028bd0b2986d0a0a6548bbe74622f09f6b2b234f4f6012041fdac97a22eb3d45c8fcb37a82694f21bf53841c92a39840925b286ccfae4164aa638a0ed9a1e31dc2aeacec32bce3a4a11298a222521409b0642b32ad06303f6a703b7761583fef6595c76b2eb7f886c29bc7ad3bfbd3704a78725667f19d69210922e8b7b35258fa192a4c60a0d4f1a6634889e760aa1c1448cf2f9c602411e672322d6d31d246240ef7456353ce5dc40fa250884bc166a5eabe36ec7e526e7c65b98bf0c27cfe3a2233601d417d804a409e756ac33f0661a290e1c810c732d2bcbaa35cbad8ae632f140b449e108208ae7486a1938dbd164c603ba37528e0d80e1f17e73fc3a1156d62b1a920680d7790f30faa391f01bf79a747963b26eba3703da2078de17534228a1c910020fc3013a7c9f649e0f662bff2b006fb24c5d1233f4fa0629d4de7f3d997079241ba1c882604ca109f7d29d411cfb5ed1a2757f36576bc69d8fb26d21b94ba3227da22c5a939cc2cc132471741980dd82d4ade65c5e506fc4439d245badabbc82a4ed348854a420094b667f85555f8b940561e02c3e1dcc33f00b87ce01e10ce3fdfe58702d7f45da0e78a7b01fb345ef4ff7958eb0402c8ccdc1ce92403ad4c51e0edb94028d2f0d1250f7c6f9c44e94ee199fe468f5090630e61fb513a3c98f7f05db514465ded4d809b3828ae36300bcbb0342b79d064db0067e666c1973e9533eb0f98d43947088f661040ddba076f8d08d382084861760b2e809af89bb0f27c3f020578dd4fabbc871208032501f1091030934c7bb9c624e63b24361ae9ceca36f0bcde8e25e0a75aa036c6f80cf158ab832c7acd28028ce25cdb270747d0b542a9fa2f2d77f482126c9190cc69f9dd9a6be2697a1b28b6db9cce0b83279b43793b5731d7a0220ef8fb5ac6a0875ecd9f0e8d1a3f9bd9498ccbc0a88243bdab657200265eaeae9d5b6f43280c75bdd937b338ab893a1c2f090472542608b5d07c0f2cecc2ec8a96510808da349610fb0fd8db92761e357ad61612eab3bbb10440a4e2f25cbc73058dd25bd8cb60e2bb58949104186ba96cd16504a15b49f1dc9b929d8904cdc1aaca00d629a534b33b69b5a9643908674b285ceef1860d5307cee7f70f26551dd1f2312c48d0f7538d568458d22000406b951e3ef90ff21715da416bf142fea88896df17871a6d3b6a4a4cce44bbe8731c2832c66d84dc3ae0f378bd8c37cf274d5a4baa92c936626c261c30b020f96d0602447c3a270688cb058c44ea847525769e073a38cf67bbd476626ec96df17aa79800374cf27002f684fc77774ab70d3945ee9592bdf7c3d85e510e34b0ac1e10069f0717e218aba26586b40ab553bf8fb6c8abbd2ad392e78c31e19f59f0332f9cd8255b56196fea35c6f7e0449cf922512b9ce0eba76d5dfe525ed270f2a85257cde435bf6b42f2e9cd092f97e35b2871327b46b470fc24dd7908d96ed88b906dab94c4e9735b781d70a1099ede634ec1975221b51b04772c8433089d3567768806865a63ae98dc103abafb7676280851250167ce068fb9853f5a926acaf28839d80516202faf0b41178c11111ef5fca5e63dffc012a513e2c90511a837345d468df28756b0a69018f99c6f4b9885c2814689e4fb1273f8ed8cbc433d981f7b0760c98cd52f2953bd1577526293a6cad768c1209d1a62456d05fe8148934015fff6ad61dc3dddaf1e50080eed2911aa25a73c37cc05e76d18460f1ea6e1a718b8caa048a6eea38cdd6b874e83380db47694c3a073e1936b3ea7b81c8353cfdf190fa3fcee9212d0dc388e5f92f2ed0d0a78315fa6ea42f33977f62b1c0c3fb2686c0edb9f136eb52d6ad0501d4d55e712ff4bc4fc1950ddc892a14f58c787b8d6e7de41412c54f81fa645c5e4064fb37037c4423ac16823fa4c0c763b0e7bb2a12009f0a61985c88f15b903ee89cb2443a77e35d775f875ddf8ce5c1eca78a7d0d64e5bba01a787dc5923b2906f61e073f75e98bd4601e34e531339efcf7725bc19d0addad472b67820cdf4c2de5b1e267ba35b5b44483efed253598e51cf17dbb04d60bbae01dddedf9881049ef330a321e32706d267de441a82fdb382fd8bdac4c651334bb476f3ffc9d980298a6a1ed1efd7a35790de0fa09e7430631228cdab4c5051fa1fe4df287b02d329ee9c7c0726694f843c083ebf85ea7ae84ee538f863c491e8717f8ee846ecece3b31af72f3ff2cd639f742b11e7d609218a5cb4c46e4132c1f9af00837d55d9cdd8d9b22ec26242cad545244a7c0ef5844e82bca072afaa069f82c31954ee201bc6dd4c81d6a77224f01bf3f14276fe4ac821d4cdfb4b56ef0e2653b381141fe0a0f386e752928c6df2be66bf712f5a07d2ad8540a23cb31b6794921d2fc044f957710d2ffc2cfa39432cc02eac6e532695959e01921d1e547775db337814a68d5c021991c873f2b9bcd08ba8bbf08051f9b0fb5d65445e8d2027f097e93dd70322806f25ddb68b087148568f3c10ccdca1d411182add1d06f92d375735ff2ff019042d01b99fc248a215fa175345d981342aff8c1d52ef837f0854d638c085ecb7c8ca665e05562b64d349172d37bf906ab509b35fef2abeba6db1005292b20ec8ce150fe3a635287272f1a484164876bcd7c9ff9d369d15490ba5de5f340e186b0dbb5bc569ca3b4fbf0c9bb6cfe1877c5ff5bbdd31c886994ad54e56fe1c27170c6160e101e36089925d5057e7955773c4c56acb1bbabb8dd13eb01e1aa8fb0106a345cf124a7c58b80fa1695e9769dcf8de89364acda41069d0c0e0b760c6ecb2096862423a206863faf4d0ea777c77ea0e3c3606247c555e89aec67409aadef4023530bee189488f1f1e04e0696aa417eaf6347a6ded350a4aa8dc1d288662e1e546fc4c9d5470addcca761a6b64c80c312b57bd16e2a2ee3f252157e573e41352b3ce1ac08ea3d9c37a57143bfdbbdb3c24c8464cc0ef9d24da7b89463ce1d8b21172168cfcbe40b0b1d8931503f2dd5c28550cf6cc46831d39db87887eb6e1a19a80ec2d45d06f8958ee5b8769511a0ee6c7e09063765ae9c75efac13da4d99571e0a0b202ce7ce4870d38d7dcba33c52344682e645d674534d3306dc6e3cc43b2a32b03d5072f2445ea0d34c89d5367f1fb81d8248b3c53fe9cba390f07c4fdd3dca4317de56d6270a30f1d6eef8aae1c96bb787bc01c4ef2d9aa8546e08909323b47b814e291a9a6793ae5850c42a849061fd1462f745d6ccb688ddf7815015b45d082ebeb7b1516fbea83a08276e65e7ac87b3486a4d06cf8d8f47f007d203b6f78797302783bca42ea73aae0c2ad82fe34b3f9c32bf35732f51eac460e7c65acf656976db4a72426ef54a8da80ca3e9d65e4eb769d4bbb2c12c955caa1a41c3b44fc852d5e1531f80ac2aaa6afe76c945366a3721a57b9830a23bbfc2ff8201aad25a29a199d69ea0d3fabd4e58631a875a274c4296ad477acc8248d47ae2bb604c34fa51aba18282cc0f4f7b6a8d0e4ddf4c5d61109a1e9ed3778e4d1bf1670ee9bbfd78a11ef494630e3e3d8bd43a9147d723e0a880846195d36142c27a0259f604a4d226c3e479d03401dad6f171ed9c5ba901819b336e55e5f14cccbf3d3f0667496dac1ed95e1a994d41f672dfd6aed2a25546a187e96d2b444328693f247078a4c69f6dda0887078465db6f54cae4f34ba44dc318a2e4ab1f95bb312287f2e0c426183e9ae725e98680e4998e3aea25f2a5a32c15f74ab5c2fec20020f6ceb97f84b6108084de2782e62682d80c846d933ea0ff91e4b8369cc2f081e06344291e5a80e58d01ca32da1b5aa0c0d808ebe3fec81d7fee9b2fdb71634c8ca0b7ccf846ccafb3d6f88931cb4533d1334c682b4d33ef7b81db25115eb00f619e908fcfcbfe65118ada177977675bf166afa846c6eb21cfd0eb6f6de95fa18cb06511901adff872d7331ffbc3e3ce20a18cd1346ccd440942c517d2b40666a277e450a0ddd1fd8f8325ba57a3eccce1b0480900995fb7aab6cbe3c2c17d6a8ba55b76d90494260589dbb765a46fa4ec680fd750fb292928e94a66ea35ed953f49060f5ad0a4ebf0fc73ad6fc8edb4794e46636a27d75f91e624f036bee505fb4424dcbda1c3ac5bba2d0630a3ba8caeb78fe806bb7d59c213d093dc665ff1c4a94fe4b3fd297f3f95d3f790dc25ee9c6f125a503d70fb307bce582dfa1d614dee453708e4c4129aa1c588c708a6b88505fcbe31adad97b591787b0331426d4ca03740e4fa873fd8bd30a40f358c4df71226785eaf7d330a099522dad352fa9d1976d71c123f977713c90305b75527706dc925252fef4b9b4d2df07c6376045ebbfb473be133a412d68607a52ba482b39ad2164d3e4e0cab77690dbc44409834ee23557e09f8e83124161e43340944ec131decfba046d980709445dc0c45a629e5f6f41c6ca0ff0440ef89f654e48d6d05cc31cbeafec0efe8c3649e70efe11e860bd26022c335d6b5bc05d64507b0457a50464fb0ab65ceac5f35fa28fea04e915ea55d853f74371e0dbb2a0a38b6c2edf759753c90e719bec2ddc348f943ce7819ec60754b986fbe60c05ad4e4aebb827e02d36ba38694257963fc143d3614000c2819385d719c8de51c4abb5de07aafb7ef92ef938e1bde823f5ce2b515d3cf0fd33d053063a934041af376740b086a2f5ed5032a93b80231d72e2cf08a6bb929a80f0fcb224d0183fe13d4379665262094981c89ff4735c9491da3d3757c4c166bf7c3b833e38cf7fdbc764b98866e8c50f142a26a6fe08559993a12643e42ec75dfea0b90bc5eb454f28827b9cb2d6ef3022ae663ffb17da01f438848c2e04dcc4e2688337d9efe8b79cd5d9c69e655ea689eb6f335eb87486f843b681f9a13fe4185fd726dcb7dc5c122b13ff10778e41ff63b19affce380189460e8276158b8127df4103e4a2b79d03b98c2993ec62628a28e9547c647ba3aff5fb6900f8218cf0dc325e7f49559b2f907d788d80390db596f398930b6490d3a72e5d9005d1fe82e39e983a42f9e642e21716bc2b02e669e2f454d8f165bb77f9c2b8047129384d743edb0336cfe1eccf1b419c34713a56b040ced7ecd6dde89414fc53024f5a7d9b2147388eae9f5205dcd1d618c19f7bb1eeefc506b08bf033ca43eca1e27800e07374a2ea422667ca5b85f7a8369470b9de07ed080d1cf79727d51738ca10fb77b9b8831094d365b33fd390707f077c6ba8d7d359a44478b0a51abe2cd648fa54b62532bf4841cbd4234208abad9af026a2754c9f7ad0c675df65165159005e22174d9ebe9053235594fa5d7a887f0499ccc8137d7832455fb642b01789a2b4e8b7a93d4560299835170318579edadedcac7f99be8fa853208d04deacfddaab10b038ef0e97849762fbfbbedb1f5e44f9e3d2b7d0f7c9b620e25a83a9dcd82aac1f4fa006f4d04cc8c967d44ab5b95516a5689513849318fc1494c99c8efd31ca87e2fa5137a3b0575f092588cd581df4f7372d88e2c4d0d1e10d216dd94f53d9d13ff7f0950c272e69879ea73fdc6957da9b0239c1ac1244033c8eff8a67290b8f44e7e8c241e33f86213a43fb6d149607b2a0596489d7e42518149a43f90dd9a00513ae2b81ffb31d767e6aec59e60265dd7744faa5272f4507ed6e73fe96aeca6cec7f9b57b95729b68f4fe43e28fe9eafb6c29128c168a74686e4ffdba39e6ceab7ee8dcaeb6b0668256001b075e3beef63142d6f8b88c99ea600203d366d27496bd7d38d8f33ac49a7c2d18b05348e9a811fec408a05df6c1f663021c7eeb99f5d49ecd13c96c5cb97e6ecefa6e6b602a5237c2f9e565b0f0480e76f9c428c117fb548fcb20ee844ed25d2a5229b64a5fe480d4f34e94467a00779b31e4548ba44260374389d0cab285aa9181be859a75173e7c3d0c6de24236b930bdde31b19a558fe7e51976daac3a7307e0e1ec6708971b56310e833741e6b70acd665ce76952119985046eba40f40bd771dfdacb67c1c88bbb44c2830f5b196dc77f8da280c6aca23d6983552225af5ed9ccb3a6e3e78d6fec39d38d41e8d57b1277c9f9bb5efee10e780a388a0147165aae2eda72bd15cac3f8eeab8208c1f6b5d53f3d5c344e9b70db204ca7df5dc15e74ba4f84f3b11d64659f676b9fd8e3aaaff37811cbb2f144f2ce2e9c768afb381bab1011fb65b16b556debf787a889214ec1f3f4a0b8b02a387851014867f02abb9efd86c302f0eb9896bd6b1dc9a6d1081a4bc7935d6fe7df059cdc550234e41a140fcfa8868d6083ac977dbc996097e00b49c02b82b3058c774095d2d75f3dd9c47d5d353f9085a6ca007f1dda4e09aa518fb130dc4cc88462043a61939ab5ab80e58a70430d068ab4570fbc2a3e8b1b4a1a0cd579f1c6870f4d0a9d0272b506d1665fd90413d84342ed92e110988b8b85c87d2397b4d7f70d5457f692c964ab0b5989606fdbc33e44cbd3b2ac42aeb2be8bf247a22ce7beba8c2d4360bca4213f166bb587c249a29962b4bd3254aa8019e8bea5e66ad9fa85905bf62cf0c751b1629ee68519e72f3c19007435e7c54142f722c3117274912a83a2947c83e917021c833d8e89390e64604fc8f94ac5edd02ce09d605b7692f9b269b9ab71559f46b672168c6529d6fe193dbe96f25f7e0f9737b1d40bdeeeeb6e7993c73d35c4352d860a8fd19fbcc5812aa7cf46b9ff74f32c87729af8397434f52e16abe6c321667bdfe55e5bfc63f9ed97f9212ccb061200fc3b733b5bc512940aabfea8be4e950fe14263c3a893c5e8a47f04b4cee70caf68217a4e164a7cd173e18181d54adbaa6f32216606032709ee92e67742eb5aee4a484bef5884bb9022407ea511bcc88033b55da4b7478522c5641d2d1423ac5ff71563270c722816867148448e227de10178c0c4cfef39c8010fcbaa2dfaa7c5b48ec01bf274fbcfe49a42a592fc64d4bd8992e46c2909ac942b670e5fc341f49d0739eb2e6fc62536e85a6977855998e404ddef6b53511140adaa5ba60f98612902081c0935ced729755ac96d8815ddbeffbe26bf9c0c6adbf1564680b0ffefdbe9e007f7494cb37d6d2e0bae1cdc656cb07f362214fa6f707b9564a38988e52730e55144b258eeba151eee70427b81a4156c79ea0498ab5a0bae50ac904de83c06324c0b45645f3c461a2489c6ad4a7a5c8da5d85616a5d515972ee6e9ac678551638dffadc03fea8ea202e14f1974bb5b635e99c4e1914b7212b239e65a7f6dbee9912fa0f99d9028417dcd6b3e5800291823edd6b8fe03adc83ede5d47bf804c49e073bbdaf7c37c22619a94eeed76da765436517d2ac5c92985d4911a22d3da01cc6d77b1efa9f865e0ce3c44a1c6316f901db9d0e26077dcfdb9458e0a1dbc089e94dd655bd8227ff7c12614904611866f5615957739eb332c14d9783cd6e6803a3cf91931adf2e4b6e062a397d6455b048f152006abd32f46b2029729b8126f8cfeffa615cf9633024749b0d770859b8188d0965ea82d7a8d52bfb46cf95b62048c056d417da344a47b325b824e8f0bcc24795fd265f520544aa7f6596c31c69fb92e8479bc6b6ef1fd04e3718ae9d87f268c04785d4a4c80e8d3d0db971cdba6e7e4c0f104bc3f634352869611a7752f40f3b1910e880eec4663e2c80d18520f0b933732e4e7aa1d9fd6208a304930cf4ba49e043b84c0d01c2bff4713d32028b839d0d8c2727f34fe922454fcad89a54a63b5c18a26ef33645569050f1fa8a781566d494c3418f5619a0ee9535009888624717e3c7d7b5d878ed206d023d37bb3ff76f7b4a7379b6d934c0d580341b8d1f9be19e0246f0fd70a1799cf334493a67eec17a6e9e0784162b7aeb88927f153f26e1eb8947576823953889f2e4f574f960f41f0c30c4167e4c2caca0b2a0cbf7ce7a00e126bbca4537ca8da2da29b2a4083fdcae83182abfde2622f40458312c8e9cd4b667ddc1aa5efb29e98e32afb55fe64d8f4478e745080ce2b06450e8172445e839b6f58990048d045926d1029a9c0b3246a77bbe5dc26cd81a0e10b3720d8cbf801ad0b5c7900a0a350817efbcf1166421c152293dba91ceeba9c03a0a59b21a64140cba2aa3357853ade81d0dcf8680488492420fbe6c6c2622cdb8550749cfa4b888aeb1f163520fe61fd8b39e61b1d2b1dfa42de2d616d7ab9808d67d538d0b328b5dd4922d4b780579ccb954980b949caea95d09159a202beb4f6b1f191a607be14e969cd67bf230c6b3b48ec9b37c20a708b4acadfed185b4a285fbcc5c2f8af7a01624f3488e36a02727c2e7cd5cd8681c9875c3c78ccc628c6c800e99deb58d6a324ebe22514604c1437086f094f9baa302ebe969341ca8c8a3823af03031ca7af89520db6072d2a57ed8d52ed3f374f638652fb8e4c84babb4bc82afbc145696b208f0b6c26c2de89b38a18908e8fe649a05f64da13104a49c49b971f84a662abcc3eb5a01d6c645cf31ca6e52be80956e8d91f8b9896c90ed544a1da2e1924949b06a67969def00964749544244fe9b63dbd6caf55fef4351c42050ef79040022237a5735e05496d50728b26da4987b68cd9dc0f4c923401ac31cbe2fc9754cf0d19b60280e2c50318117a1626696a5813148e6fc4b2e476d5658997ef6d940be21352c7c2c5ce807a988e17756891b25dd5382d8163754d8dab0a8b6bd9e53d81be50266302a8d4e4e8d77bd5a61f3a26618cf602131e87e451a5c3534bf9ef0c61abaaeea63db1f63bf04dcac557356307e52009465960a0e21cb00578c9af461262105f7a443621018065e0cb2b071ba6f8bf263e65a74e9b12eee0c5b50293e4651e5923f1a11f272ae75f01eebedde6559bacb11d95f47b2169dd48b4bfa9cf8f80f573dca5a105954562ad4d176010d33446e76593097d545574b9881c78a1d8fd30fcb0e884511a79a2d80f824299108a2c18043f9f21cda63449f45b04e58e9a5d479ad2bf915573b1dbe6be0dd8c66a6abd42bec249a72601a84691142fdbe3d6e9c36c9b1aa18ba3e2bea419976cc0b7a9cd4449c50c7d3f67417ad84a5233d38c71676a54e8d8520f789d5cf0056faca6e1b9689d761cc5a5e4d458f1d6ebb17f94613d2681be28b33d01a99404b7749b345d2e0726b7e10f37eb0252af4c40e07ffe88e24036bcdf52fa6486e7fcb6b5b6b596c90cf46027aed051c76cc743be2272c39569fefbe635e2c24b3bc959658ccb5fc03044d5eda48fb39bd9f25845f4cfaa1f544a48caf8055878b6a89571070a2433ece15ce2d812ce98d291d8589f7afab052d1f8d4d7629dbbe87290626102737959e0274f76a7ba5c19bded190effcb5c43df38354ff774fc87c28c4a5f43e94edfdd6f1f5109ebed8dbfe2489391def175b4e0d87ffc37ea2b9a9ce990653d1d47c114c1a99cbd417d0bb6cc13db0c6d77a21763f2aab320d44286ae63a4598b37fecdcab8e1cc9a2b849cabf495d0f627428622a7b754b8c46d2f4efafcd1e9cc301d46a556832c22590bf44e5daec34d50b946aadead6daafafc0432b13cce4bc4a3510b4fa4de3a73d4eec0709f352afb4ab20aea161cb4c0bbd99d47c0d0afa0dee9c0baeaf766c722a359d9ec8a8e7757a96c2181210aa2f60f20207c3debb0f578767dcd299b493a559a6db48572c0b65d4f6f5f94256574f393d38dc6f2e781511f501d2a215a93f4644f4f2d92bced5ae2252d3e5328a0267d18839c471f6c5f554f4a4c3fd04dfb0bbfb20d873beaffac455d6268d3a209b63c728e2fa00fea05cafa5dba34beff4730e21aea729630d530ad2393d5206b667dee473e3a1c20b5f3df97926253ef2b68ae75717a125fbf21a388b5b4c159641fcab6d13a3a8a9ec19047b1ce7ef6999bf6862e8f61c475c1c964a3356e7e378db6297194c2b3c36db2de8eadb01644b1730adba83bad1c39b734936d2b6e7de8a7363219886d941227092660e01eb93d23a81f119e1f82f745bd13743d999d004c9b8a533c9ca138edc05588253611a58a8d849ec6bd329fec28fbbcc25090617de08bb7265c22f1295a3efa5a0013e1011a0807e5cc99bdea788aacb76182feed2393efa49c58b081e78ba6a6b554af0560d66c34d75b4dd91a6d1d4b2d38744e652fa2cba60c9a985d9fa015f30013e3ebb8d0fd5d5edb157f0cabdf4909d88d11fc108bbcc0932853e05f35f2216e8e5d640e7c36ab059e3234bff2f85fe0d2989ad0d2006d9c8aa664dfa31b042e88b98c9aba96e542709ce2d83f052731ed676c5f93d1c51fe8a86635e75c7eb58e13df6909b25967bb83908bff596cd2f82a20cb3cd66e96e9c87bd2b0aec228609600c451fc1fee3ce08470bf89e5d8cb2a5b1ea247bbbbe01e8156e27486be2123f973f0f9f863cb1a3ed1902379c24bf217404e7964482a0cdaf4b036858fcd5f51045591e239291171980205969873f88feff0e1281cfcd3c74c98520e3d4c929057142383c48a720d22c5c7f04635f43099433cb0a24c48f638152e2c373c16a64a617c6afd6288246c5f41c611a82e083ec8875c5812528df23d5d933c327eecdab8af3816dd64e1ae06027b87a6ac812b44b973df66d1d219679cd209e8d1a51ae90c81aa26bf17a461768f4b9880ecdae2cc88d1c971a7d45fb4a435495ace5461c90495d27455edb471603db9bf9cb038f7803726eae88aff077f93c2d3c4c51afc42389bf8436478a6d53f8526aad6fd9c50f55a5dac7e61dccf906b6bbecea71b9a3c2ad926c15d02da874b87bc7fa5598ea4314b90b6f4a1ba4930ed6f09369b498e948186e2182a956439468c433bcaf72d44af133fa025c5c9be38f56d4868f417b48e6689c84300aa220afa467092cabff173d4c29458536a15791e53e24cefba1b6a5dcf13c622fb17bc38f80cefc7fd8a9f4623cc5896eb1daeb15b2d61212f4441e0bd1a02daaf809bf4353456f1470c604a9d4308caaeb891b4cf6e5432e32fd99c63f27e2ac9eca43e73e9b02fe8ee77e5fb5b3db099d8b2fa4aa04b9059553c478a7fa427b1e40224a62bdedcacf8704715d1d2bbfb18cdb0d21d95fb5083fd6aa77d1daa314eb4668435bfb2fac5b994dade9493a48f959ea94f05e6b29d2deda10c311560cb390f00255f1a0ec3678c768c3925bbe233d9ce97bea5463c25c66b233f995c5ba92b63e668c72de1938ce4cf31a140a3102cecedb1a6472b168de6b832f2427af0654c1f20587a53d751b9760216c8b8baae1f985c1f48d66a0e56066de0e76a3479c529182c53f834d03a2142cea9daabac0dc42b8fc0c3356421ae6d591717116403d05597f5cec2b530b8cdd508be0f35e58becc59ea06a02da94bae2f8c2a97f506dc49bdfc5d7550dbc87357a8aaffba144c8ab822abefa6461084999701a663dd5289953fef605f5d0456034549587943fc5eff06586e1cb62fb55e45e943aba96baa3abf0355143e34cbc1bf0dd97adbf7b2a8ff21c109ff0a5511b6a0fa0ff08de66a7e84f8e32087c4b4bcb5ba3fa70802dd2a181f8b3216d6cd84c6ef55d3fb01ee8273ff3c12a82cf73189f87b0e4b81c93ea3f0a62e3be674075290a82c5b4b8dc54b21886c44736663b6ee147c9f8914265cf6e7538aff0629384d399cd9c5dfea75c661a2a402f0e903fb00438dc72df28126118892078b85d01481dca3745fd0c609be325987fe9cf02b7c28b2ec3db811f2c80ab40d86b4274a0578f9e2a50f07e367ad9f25cce1541ce5d74bcf07b62c6e05d77b8c4d314876b9d3d0624492e82eee0d35d0bca0aa899ef2de12057e7057c481c4821212e0fdd534ca3721088cfce7f3c5e39b9b5d2867bfae49a9c7cc62c1f7a0c4cbdb4553598b4810d1b355e5488184cdafe62dfccc7d348dd01e1f3d19977479f1eb25b351fb41d1c3c72968646d59a88045327e1ef1632c8924049a009acab33342aba9c31d37c238a82d23b4555bc3d83d61bd330983adf224a9e0ab5d06c4373ee66eb1c6f3b0d74602b27f71b48d468b1d340c5e3a423adb1079b8175b5c9287695a40b96777863c018251d3ee267214dff0db6e9b56e0a1db96b382b0aae1798edba97c98cb0079a30d968de3f4752b071e6a1b3802accc7ce2d06ecd463aaeb9fbfeed3408d0301c997081c8d64a3efe65b629e7c5299530086d4dccc3ff0f518a738daebcbf080b9aca963c76e93cde93c44f873357523446696a77ff493cb78ea870c4402df1ab5dcf5e55c37a0b17649998d19a9c8d8bec7a9a8b4ed2a50dc8c002c335e27cab05c0f9b820d693e6e7875aa7189e6aaed7eaece235f4ef1e982e4d49baf2fb22911c5d529bfd58bc6e48fbc40324f6ad18ace85a49a3116901c6156e7109e045194556d3f510673fb624406b04332fa7e1f22603fa6ff204f4468e12cb5ae21a861bd417b97415e1eb3e9584bf6c0279ef55719c1fc643baf4e1530eef9180d2b2360443bb753ca5c2450eb78ac9a658289a740db1f37c51761b1c3bd04be0767843a2b5da2052257a918d059b728cbcf1a847efb28c54f13fe494b48fa7029fa94178445648c5e0abf155af04d1124daa2a685dda5a5a18a5e822b23c1066380959fb0a8fedd6055cd41139ab8f49411ef1c1918279de2c326f9d8469a92c4e14da7397a6e7f334a3a12d3ad87d496151a394c92b1ac7a68c39af9dca27ebd48b5587cb295466080af548019573b13047d03f5cd72ebe8e46577a8d09c7b17cb43610061f261f1958b62a00e193a5bc5931403eff190e9451f220c6b221bb2f2fbb26d6c1a8aec78b86db38d2fc7054678096caefa5247e000e7dee19d9449ad0507809c7a806599092dfa61a402ee8b657774a6c870b5fc45b32535b8d36984882ac601ea3216e0041538a42b2b7829c69e24605c84b5e3d2e90694333e107c0840b1aa0551af6ed4cb1ab2c279a59395306544446a6c931edc8c6156430b535b966f0556c7d496e87a5a4f2234e14515dc2581f0a5acae27e92442a57f5a466dca789016c9a80d98559ae762ad0855737f5ab0ad4cca990f7aa9b8ec51d22289fd85552f42658dff1657091904dc95d8ab2a1d5a64286f98716c152f24a05f94654c4095369907798b967487fd61a344a515b7fb6693f829da1b8a893f442aed95be6303993a72296b158d532cccf902768744ba1bde2d7fca9691c50a9c8dd3e680eaeca26705a27cf397dbca3e0c9fe71fb220699ab9958f25123b39d46d7274b392be1f7fbf18ba56b7984481dae0137aa7934f89b2d97af2b0d0e664c5724ba181560958a3a76c6f804aa82a2e875c93187ff66a05edb0bf6ec6df8e051190088cb572ee20c65b1ebc2b830ca7236171b203e609cbbab1bb334d0c157100b6702a38c50bb2b1ce511ed5ff77b0aa749d16f7fbc6e4574e1c752b129d8c4ba82574e8a0fdae053ade3b905531e2fcbcb6f7887533c7c2b0d182da97c1caa988c1bb95f6207469aceca9b34758c83b4fdbb502f151296a00a54520bd280f79b9f4ba4d7f36ff43d72cf9297f478df156a05738361c84f50bb328729f076a744de6c77d976f68fe56bb68516f02fc839b386f564a199cb75829406d8e6f34905a337058c0314e706d434c0d98b9348e35e69d95154fff975c5ab51fad6cd6fe79f125f5f1b49a674434faf3d036698ea738c3af650c2141d6564adf77b50c37f87eb5bf0c348c012825d94be69741bd0b85c6c9adb610c36dcc98322b543b729d6930b5744687ee756fe75ec3f4c32603d58d0404c4497ec6ddedfecf00f9e9359105e7b855879ee2ed34c140ed6eae9b500443be64180b5652cacddbe1e6551d72cdebfcd40b6825594de3cff9655c83bb5905e4c3dfaf67bce62797070eed6bdfb26a678e5c9888c2aaf3d4b6834c020108f9b94878fc2a927169b511d01f59321a81fe2e24a535453b78e03ad1d0051a2c190e113b3fde9564e2d2b3943d374807118388e8253f1038639538f17498f186a1a26d5766e399cef0840faf8589e141b671fbc474ce278e371d4e319a611396e0e833817f2c9ad881f2972a61f82c806c040969cb1507f04060022d507e56ef0929ecfd5126f7a4ca3102328bc72187bdef3a2b56e63ea9b56acde63ee162f0604516ba059204c49b276e46ffe3c2a583ee3aaed2791854599153006fff70ea278ca3016538d4824ca0a58520b9182aedc3297be08ad347b87a5bddb0e5a0b2c4743f47ff8b5e4aba8088b353f726157418efc9f6014b654985e999b1d3d132b799249f89fac1cec5e020e542ef78d7684aa5ac2902e9506673be91572ed49143ea884072cb7b321bbfa521dce13b13edca6fd85c78e8e51dd91f863a3c02e7a0469c8f471f05d18c548568ea9290c4242a67a47c7310d8adb04eeeabdae31911d04f77a612044ec220e3dbb2ab3ee2adedb2584d23f3c0dac17a480560a9db14cfa8c8208f0e1b7c5902aaffbab569047e641d75d383af6b0d9af8a43599646488112771fab229ddc86b402a34da4e180324a8a8672f5b6eb6f1b14f4916d5607664666b30162af36096697cad285b79a4ad436cbf617eddfee9098c89d78984ec423e8755542b0620f335a181780bea2f9d1af28ff97f861fb1b986f6860d5b5030fd1cf1d553dca108c7a70ad91ed70fa2fb3404abd9cde672adb481d205425c9e3a1a93b2d5244e4977b755c281980c68ca689d01bdf6f6a3ce61e84e1c589f08f4df6c19f3b82d6e5151e4030a20767341466fbca26c6276652780c2327789c2afdb93999aeaf328e379c7b2ba5f648f1305a5b56bc9e3094155464142f9a64d855366ac379a196c34c66fdb1a8ee5449b02665ada89e773a4c87fdf4d6f8b3ae77b7b989c567c8b2b32d745e7d6b6167ca80599513ea05f0860f05a83cf816d51f7d33c19cd5cf596a9768541efc7532b08df100e3798d0b06c281396131406c076cf214d4a07c9192af2f167ae23075e44e38579db6d3516bb55181ecbcf614a3c09fd242fb1252faa73e0b0c5b82eb20901ba607b8b6290bcbaaad164cc9562f7576d8dd7ba3d02e3fd2e8ead1bad525071cb10121ca671dd70dfee4b0db2202f33dacc6836b530315636564e04cc0d948cabc6227210d6635b44a153f11e2b8f74a150a72fdc90da64829160a7533904ee17e762a1de9cc8f34aca8e94ece5184ebe0bf3ea4e8e7a35e29d22cdafaacfa7141966f31af981f2a742a66f68049480c0b31ba8ace82e9ca146437e07323f87762685a03382273b1d4ba2290e641fa9a205fafdf23469df978500bfb8928c8046f798c05665c1b8938dcf4089621e97ef959ba34a139087c58b0221f51cc264dffd256b34abb30b19cb2ab41e2028b8d5723e416fa9fa311a9c444f85894fe51870e9b2be125f45b9394e46d65abc6e525ee0e0de334663c10dbfce53b2d2286d00b6f2ccb732ae249bb02cbd0d92ecdc28e32c3b746799a6ec19903fd627ec7c6a7b6e6d090fccaa38d3e76c0176eb7dfbb6f315e2c0617a6e951b71f1bc80cb2019ba3e044873f8e4b42304b0a49fba3904155cd1f89fd50f9d588d359e3e53f6b238dcf29e37e60d9d2da5f2f2c3b9bbb636dd2b25290482cac008aea297fb9fba1d080a758ca8c1c73f4d7ce93470dfd81ff28cfec383edfc76c17c22c201cdddc35d2d7ab3da9cc4710397240c4cc354c5f5548fbc5178be6b19200df108def997fc065c1778fd647122f40e91082a9d13a332125ef78d93fd1eedc303de88f08c56bf78c160d17b862cd9bc26541e101f1bc3487feedfb6b81d1441c93046058d1d5f3c452be73e243b92232eb28e9f4b31d61a69aa3d6166589421fbbf952abd099302467dd3d73ade0abc9c8485b19e5c7157b16d3cf2acdb9b96d5edc47dbbe87ef74660712b828b4314ea2b4308634a77164aa259f26fcd07aef2fa568d0fd37456f616cfbfb319df857c828a81610d7ef161431a28c619f6c7d414b6706bbd4195f0d3d08f13c94d113c06f9c9ecf41ac54ce88662ef7ac306ada45f8ef9908764ff8dccbb4fbb366442bc3b29b42b4dba0458b6508cff762833b7877b0bb295900f312f7d8dd99495f68fc50c87c00d767674c529a9221131b7f40e669b9995b7549ad49d0e46441bbc2bf933dab7c1105fd54d7583f8b68ab14d4f780123861e06c65218dc6526ca9989d2af2c2c14d1e4b30227a30f581fbaa1814eecb7c09c39ba8792b7c56fb29536e2f1b2990ce23e0ce6125b76d932461faff00e58d99789e6f4dc01eae7682d7671d9e522f2270cd8a939e5c4f3289fb1c9707d7e542927600d476056ebffb6a3e48fb845c491c36966ac2f66aede2d565d688248a1c67514611b2918e21f13d4c42df76012d412255d2ce85ca5c36f9c414110beb69dc303d3b6679a3434d404b31d238248a73fb242cca97098b1f99a55642a8a57691e5b899fea56c26c0af76cda70fa8977763a713a6c131c5b87b45613e032de1ebff42b91c171429083c1424ee31a60ae4494f2e8392068b9d67cb951112031352422c41390ecc32e1f87188f029224cc3f1dc8a1ee9a1f0773839ca91cb986094b76604ed662ee998618a63f22be44a5a5da92372b68a3b45a32a1318e743a7ba820dd5cf99d43dbde315aed47003203f074f9d4a81734533668e64054bc0883e0528eddd1e354298c2916fc266c520070f34c2a0c21c7d8f3cef957ac07385b7b6a87b169167f40d52e4b45f80659086224be2687f59bbb5019f9310bf94014e6858b3bf9cc3b001dc0fc2d645d9e70dd68281fad665897a414d44104dd25ec9c2f43886565c4841c11304eebbdf2e4f90aa2b424760e7b63dba13e06290ca4fd9f3c943eeb14ddea9fc9354a0b45e950218985f0a3e26005809cb6b46d3ff1c857e01e70bc01ecd74140f4c28dca074fa5b02dc41af40832720425b59ed319a3990418c94a7a45001e925f0a03043d4d96e722c687165c848eed93d3d022974999700263de0a4357e42a9e52f7c207046f8a5ce77841467c9152cbd38b229548a86d32401075194827f9703d3fa1c43951cf80747836cecc59c4a0776997b9711919aa2ff2ed616d781e470d04d30dd279088f25b1a6ec1deb4314b14136165fb9ec59a56e756e8f4d8a282b9297547b80b399101ebc70a7a2017dbd946b599e5a71973b42cc952a3465bc9a438e3a5598d05f8f2474d66429a430bfa1eccc46d4aee80084863515570a5f6495672355054cefdb6a34c76161a1288f61b32b08b4ef6cb71ee5348670d16861c82cd9da5f37c83f1e8015f4aa441ace721499e759cbd03432355661588c997b843aeac4b9555c2f3bfc3e8ba9de67b832e5b5c14112f45b6b7fba2b9813b3f097a11128c2999500e909682d9a49a2a535499b0d46925a73c33b54f73ceed03d0d95296bacbd4fa1b862817ed28fc4c37854f374a9066d67cd43db363d66218472023022ab02aad746ee19d5860e80ec8a2c57c827d5872c2477faaaa0f70f6ef4fcd6d6aa8c032d50827a863c00d213f0f24ccdd25212c216244ef4ae1096609180e64ad5624f9f22007d401cbc2cffec1130aa6482fd25146557a1eae53a42909dc1eff56dd9877f213e09e3de7df78a975d86c13611788f8c225d4ab1bcda6fd9932d0b1a033b02342e032a98ca895920ccc29a0e1daeea2bda631db2625f94f4887c25ff4056630f745b4df916a61df291aabb01a4be9288465bf8ceb31af3b80be0d7747ae0bb56532708efe64eff7b774b30268de18b7d03762b1fdfa4b5bddbdade7b4b99520af8077d094b084ea3663feba36dae6e3b71bb0c2db7df031d6e3f0e11f6b42cc64d8f447f5158e100175cfa4ccbaf9c22fe49432c8d9ca73f64204dbf9253c4f7c4bf1df9b7d6ca3486a20a14365c50e19ad89806d822095408a49481c40c2633b3d8833a4926552a37686e8b4cb23636555c900ca5ddddccb76500e8baa694524a297d1e95ec4bdf47d7b928188c262b12c108080a27dc4a1572eff0ea9466826daed01d14b2c7eafbeddeebbaae73bf3b4694f66018060371f0efc81ce87bfe11c0eb0175d64998cb3a1d9c22f6146fe70e4477ba5cc83b1fed4141190751da0714846596c14a56925966ef19e461f67a89420ab73a78dd4b148e6eade709d70c1b36db72bb179d66ad74925c382868b8dde370a09e713b5c1a206ef73c705038ddee7d388dcdae4de71c2523e4dee1ce3efd618fe01d883afd522e3f0d90dd8ffca0b5765f00895730b6bc5dfb29752242dea9f2634a7d7eb0d216d45b0009549225e6109a2bd007d43d338fcfae676616c758e5905f98d33a897e1cb3fe201e3d7ca4b9fd4522f41bb1c01349845910d9a3e84917c9e648b229de10a6d35a6279b5d29d64e4cdfa4c6f76afc6692567de256c1a2ef318095505fab58c81a62d779e3692b0bcfe5fce7acf4feb9f43bccbfe94afbb77db079fb2bceee391882ff891b17ea4db00b0d749b014c2ff7d0f97511e6f91413d8a786453345b4ccb34ed665e59bf504abbbb8e92bc4373203384826932722ec3b1d3ef4384a212c2f7689d0c7c6fb53ecff33e8f2c211cc7710c43978b9ff52259027feb791c623224c391682ccfbee3b7c46726593f927cc5cbad1d1c33ef7f6caf8c3775ffc8b853e73f87500763ebbbf76cbbe6ee4dd8f2b6d3ee6edbfd1d5989d86b7b470c6c9f6eb54fbf0e217567e6193ed87376ddf5fe335af8ac6fe62a3d60b1f4407e204ad893d1f4b3be99f56795278f913198e7f2e4e152c2563aa381e5f46a4c183b45098272dc5e15c316ebbb451246791a245bfff928aa9d116596e75e15c272964f0b56a25a4ee9be2b2d53da775bca660c7f1efdba8e99d963cfe38ed97218cffc496c7721930baacc4fcf19ed61966996f270fb92e537a3c3c5956409d4982ea8ebbea01ad4c33befbaaf76df773da80758672cf5fe4490b03130b1dd51faca02de68eb06cbdf826557efa810e0fa4ac22a69a79c9b59fda7b4efcfbfb5beef94b160cd083d62f39899d9fb3e9debd5659ec73648f1ce40dc3efd3290692595b7363773ad944a1132c1218a51c4284cc476d775cc1db7a03417fee52c589fb988570599ea2461fbcf9891f73db7bbb159777b6ecb89e53fc1aa9077ce6751944047e5390d7cf1ea941f4bb1cbaad3aac9fef5a4df89d07d91910375de7967e432a9aebbaec8a8a3d12c55d4cdff572868612f9bb9aab08c03c11e62dea4b9635e782b3fc6d6b50e6a659a9a5ab4ca57d5c46b9a6ceffa02f2698d7c552ffc185691afbde0dd44920f4d958a2a35ff19ec65f6555eaf5e5ff5bce0fa6b04768af39b20e9c03a93245c135701f581d38f21c2b666c69226252ab31d51a732684d1c32bad3420095e0f6c20b8c9a7ef3860958f3015a66b6356682c13853189b425b8769943097d92cffa973bb3d02a5374f61cb5bdfcb57935b611bcfbcc95f03a6cb24c864ab9cf90bfcae256c7929536f12c4f81d0c23a0c8d5ccfabc03529eb26953ea3e7318ac3e089ad1a81576420a435cdfbf9ebe6bc6d36755f15f2c16ab5faf13725ee75be773beba2ce7759e4712725ea78cbd9ecd19aff244ba2efbd6ca5fef7a7df8225d4fc7a1f0715e38ff7a170e8ecb153e8e7d1e7170702c0e0e8e75b95e24e822c11fc952c63d251749493d8dd90737e8def0f21615aac1a5b09c288c5848642108884dd8effd5befa4eb8641803cf84d1a0531f2c61fde9e77081a710fa53370b0defa9047510cdfc91851bfb931a2ebba2e2cf6f4e74182376cf6a62e917bbaddc7f7270f1fdfc92126d037a2b4b164a74e3d3782580671191097d12a562397d1a70f961eb8ec2badcb74cee460fb73ac23b68cbda26b7de48686d24db77efd7d5ec46dcde7dd3d0519e844ba3cb33d5c464118e5a16d9fa94b6c3d53f0e9f84388f7604e2ca71cf29b83f35d735a13516ba6c67d71292d69a412e3cd2dcaebd2ab28977e43e5e07c3f8c68f92acb91feb7ca5376bbf4414dcb04a5b6412d687cac265d37fc280f1f3d5e642c8f4b3fa9caf746de6c3d75d7e5013e8f1659a5df4919653d083eab5556e92f67ab37f38085bca7af83816ab08c3e0f97511efa2059e43cf419d67591e4db4597b5701821b4aa27748fa22e7d24c1d69616cc89c2d26f27f439d027b0d078e90f61217ae96f80855a0fb342cb13ace1524a9f9bc6bce452eb324abb142c4f28ede33800e7ed375949c0b1e5110d324b6ae60a2f354d706695fe1e0e73a02e6d1797fea9c105ff06efc000fbd0cff9d3d2663cf83b33c81c92298f252db091833541764be8537641cf7befcf6f6cc2f6f5feb4f6c1f204eea11f365f19a5a1c03cf4a45d4a439871bd3f81fe83cf224be807c57f3d48b262437d5f4dc686facd7dbd582388ff2adf65aee7f155c64018fff544e3bfca1dee713dd2e5f1f57a22beafd20615178bf5df235d90f4defb919c800389250558a855cab8c7711fa9e353ec5b4a3bc196b65e0b7bda95a2cb9454abd1c724becf5322ac890e24c475e990a3145e64e87b0fc26628ac5023a23c9cc77bdaf42778c1aaf0bdf76c9cee0797c58cf1c4f5b155f848f8de2b8f7060c0a166aef059b0d4cc2a32ca439bbe09349a7c1812b7cb29decfa857e25e46696c52296c57ea58225d18415d19e89c812203316dfc7e18d3c4ef2e4569e0f7fb596340be0661383cc452536c61334642f8f110bfb404fa0e82cef5d7b95e823702e9b704fa2c35c5964b752e25598a4a8b7b021923610c7f7c3659a4f860190302b61efc4a870a1286ff953120df3796fe64ad8559d9bb94e728cf149557519e7e91742bcae33867b1f9916cbaba7cbfacdff6c33c078f60d55aa9bb7b183bb2f1000ccfb9c060c5cb514d55d788d22c6ccd75be3b5c180701a3203d6b7ae29e32cf8c3b153924fb8a64b3acd5fac005bfb9dd1596cc3dad6fb1da470480e395cb51569eae5f2bfbe25d1d8eaae274ddd3e1ef20cbba8e3ecbbaef8e69e8740a7fb0916439c2035364e44384467a72c4131228545ff4a083e9629258c8d2c5176e8e982ea687296e5c8eea614b0f56c4b0dc88318cf0c63882e52d8cd1821d438658d7eeee4fa3bba625ba098d857d5398262cac1b08a094301165c3d95a618e8862892f59aeb8c2052d4855c66c5942031951b8c922ca0730a27c58c3c242e3568b4a92c516c5a44454921b141656940bb830575d60e982c60a245c14e1013223c0414c1566aa9e6013d68ed54e677c27e4d9813e0f6485e2738eebf58cf3fc1eb0e390cc32f7a3ebdf5e5610e81d3fe43101483240b1f0851331559890c224c7f214af3b0ed9d7e97964827873724890eceb894db6bf8ee239e4b66a37b66c149fc7d6f787adb2fac8456c7b24db6e0a168dc0294be55530291cecda99e3b880d371880235db3d70385c8f071ffc4214e89b3620786d087849ee691607fc7a42582f64087887c030b83b10e846a824cbbcbff1805b41e86ef7dd3842bdac0f2977a5d58502bdacf2b9c7232570bb0f86aff30d2e47d9a692903102b2bc0e83cb51504949d220994a22820b25aa89880e8d1d9fcb514d459294a1c133064c03753c88410e646fcdd8f35bb7dbea3211a68c2b35bcb0041328a683d9e1660729258a6072b564ba11e1185e7cb1b189618b24f3e4dc977bf2541829601043a40d19236298eee58a2ca29c345dddc228629e5dc3ddb399a68401c387324358018379b65397eb4419aa2c6a5652c2a031cf86e27215c0658b0e57446a47da98674b1581e4440437275bd45011822acc2d3e7421c3892cb668cad28559e3ca0c173d544151240910dc0a8e5861850d0db729614cb7428b1c94f8c10a259ad88074e58bda0c46402143c50bd37f02679c38e18446071c4c241aa444e144ea8a199250320fe0049b355161fc20650c93b96802876b228d175eb8984820b0a2cc92a7128c208b2b1ea8dac2892947f8b0268ae9af866575a08090fa0108168a44d1c1f42a26b630414582345dac7122b0f0baf042f0fa22f6b0e5a7e58ba803b3feb993f1d078ad963fd8b530c22f228b7157ae7f75f728254258d8c72e472971a2c4764f592b4b2852b1fcfc856d9bc711d658f7f26fe845b4b6477e45acac7dfc0359179800d5092958b58a7a180509653f0b9ad92020b5d16f27fa5da3b4c654769245324ff07ecccc228fcc5ec73ca3ebca202e4b73c35cc83b39ee1c6eed564e712d5c516eafcda61c075b77dba29054ddb6b7a390e48ee09c4710eaed6aadb47a33cbd5e02db40000597c7587d0282cb69ceb65783550bf8612769b9182c51377531146eecc0d4ea0780a2550bca439e22667e68927ae926082e90c530b5a18c80a0bfd93049a5a9bf9828a13125580b9914348d1458da508a389146e0b285a5899e18208a72a32bee4e005cee43613060d5035197235b8407586c9912936d3a60b93b9f3c4009c40c9358f25c838999d0b26bfc50983c81240982b88305c618aaea31fbcc2e4b7e292300aea768750299e98fcb677c842224a988ab0e198a240712e4cb72423988cc4d05c96c650027e612a9c3c9112c2cb132b6af80ba6225790f8c28bab9c0fb18485adb9388165044b543ed440061435fc90e6092c2e4cf962d3b266298a25d0c18471bacea57a7c868d19354c4e5a8e7892e68b29726b84c812450f422c75f174ead581e9d06f39b5c11541252a072890824822a50b25d44ce1050b9e85c154498581324588365b6e5c3166cb921068b8b069304b408458966e4d7aa9e915c4d211d0cbf2cc503079f382111c98a4b12121d3a40a109071aa4d11c4525247df7459ac5041433b97e71f253120f8828c27c4e01244850b1daad8a24917298080e2fb8eb0a7085ed18a4eab1809e3848d6819b350511265c4a99980a0657663a485b18ce5db1d10b5e98daa27b220b334822da8309b27a4d8a08ca8c209ffc1481170c72261cf9d1d99d3ba467520411358b3dcb401ee0a141b8e6c9162c2861427225686601283882b69e18db17c3b08598d3135d87253b0507a428a72a405125c9102ca49cd956e4906da87c99c8b1a4b646081cb539599280818a30b5497366a76a0024ac98a2f896ee684d2122face9a204060495280dd1024e89ca12f72e4a3134356d4272ca72458a144b5bbc99528012b0c00589122c5106e783521297167beae848291549ba62062eb46d4951fc8073fd290d83a3c177781294ad83a42a457c47eb3a97a428ac2c494b9c742183402386164b9858728314c315973356518831268c5b494a6a91b17cc12c45de285571fd95146153951ef62e05550c5dba684a13514f9c09411932708e0a161e9a7c99623353a5c812e38b4c3755248ccbcb521fc643ebc0260a23dd7129422586e66243922e45625072c7351569c105d0eba2844d13ce0ac917225130696a620974239a612f9bcd9048d26d01db9510f9e10908e28848691c53253594090386164ae8308506a41f9c88606a491119a018e981880cad20ec09a3740c1122507c873b17db9ccb65098286285da2805cb80245ca124b543e381531e119cbb77a1033a6484b578e30224c143137d8908455521321f0c2030c4978b8b5ce80b25fb6f02e2ca871e7925ba20193d7654c75252c1871c4dd6dadb4fe0c0b0b447c399b25a81da6c482aa1cce4821d3820d69cc5a466e0b1362b8a012c118bec30fdf529085fd3993053925824313087679d2027361e1c812ef92450dee5c7ea6e48d99169ed257d356d428692e3e0baad5855a6badce650b93effe9373459400503c29e3c585248e98ddfab25950adac5b6bad1504025bad56ab3c59e0579ee0ad15c6ad23d4ddaf2ddcf00250096e5e9630598363375135234c5dc0ae0fabfff565b3a0375ac400821e4e87363b9011c6142a8008220a242e50c8d4aedcb0459595272b96bc4b3bd004445b1f56ffab184b61a44869d326c7c6ac3fa334012401c5932590bc3083087cca9b340e54414adbbd1760fb6dd89d18376dbaf147a7c3c5ce70b8ac652fa55314a17750201ec4e7eb3eeffb60569fe7a67b909256c779bae2d0ef94dd19681640312d7c36c181dacd97db3fc481726e8b21a3eaf61f755241e7ec94edf8f385fe84092088469b00ce8756c4803cfb62f0618e203fbee87924a1004ff43c02f1f145e5512c833fa2f1c62c7a367d3c8f40cc272a8f623c3a9ee88f665ab8b83289defc0c48be44e451b780e687297305961566515985e7431e720049b81b728ee7217dbe88643303b22f11d954646a6610a57d1d7c00ca130cc07b507af03c4ad8f794e705f70cbaf50eefd63efe3d8fa33c83703c07e539e3e037284fd906bfa33c9fe785ca13267b0194a7d5e083ca0c9e569e3e8e437e7f1e88d4417e53943ecc8b4bdf83f284d10f40795afa1d942748ff879cf99043640e52e41efa9d8dd2607eeb1d23fbd0ef29cf201ce539e3a03c651b94e7f394274c569e5683f2045f979637bec667e0dd6cfc8d925d463e49fe6340f2bd80e4bb430ea11703926f499a648cfca137df0719234b21044842892ae691f92be8e8108359853ed1fb78cb823dbbffef6c0e7482525c720787893d71d8f81a5fe3fd3db880e4dbd96c90311aff51d520633462e53795e6bac8d8cea96bf2d960bd3f3b1b093f9ee87b24a1003ff43d02f1f144e59127c513d3c7f708c4fca1f288c57c31c28b69225d18795e500179f21405e4e9615cff0966381937833cbf31ae7f0e79561c9f61bce73775d2b8e73775324e47244f9e8285deadf342c31ffca684742d9aa40f3234637a65ac7eff596967eb6e9456812f3294614bc3859b1298fedd13a56130823543d4921841e209d3bfb3f9774ea119f347e98364336cd107c9268f0e18806d0436b300a5354936bd067b5a11a987eb505a731b665a4973bbc884eb7d254f9d5b3b9152905e5a96d05f6b3c616b9ba073bbd34e515519550f045de625782cdc69bf19f3cc00c39ef5fabbe840e7dfafdaef3db086ad1d9141f2c47129f879f44dd0a15342de3919c74c29156d0df6d4e15106b9ac8db0fca707a2fdc2f29f202e0c1ce2e8c3e803c740d17e9f617e6666a97be4f9f73714e5e9becb18906e66666666666666666666666666666666666666666696e2a7fc95bfe3f7d863b37233735fcff13b8edfa5aa55fb0745c82218cbcf207863a117efb0b5cbbf8303e0a5a10b4c6b1d14f7745798c7bfc35117ba21aef7cd6163eb15bdea98b807016ebabe877736da39f58efe9653fbf8773230cd938179fc67330eeee9d938b86767c3e09e9e0d837b76b6cee6d93adb9305bb0f75b4b0e1574548d4959f165e98e655511e2d38166251c19eb85566aa8eb8fe7d8685c29203f676ae773431ed136b4c94f67a5f8269df0550b01d3e9bdf00e57a797e4c74aeec1c1dca49e3a616f6ecac4e4f2a476939efdf59519a7df76c384f3d9ccd0aa7b49f95e7643b2e3d1be5f9a83cdbf5f728f6ecca50a92ec7344f8a766558887ed5f592aa2b5d14bdd893bac923915bb12795a244afdbbd27e549497d5a3e2d9f964f4b8d4b4b21b4c63d3f2d9daba2b40e06fbcad3daceabbaf2c10b1816760a9b3b6833127d24022f588a536cbd512997f973c0de167fac8269bd04f3f887e18c19b1d83d3faaee2b2143eccd09c1d9b92f0b5b9decd9b90f04ad3d3faa8f2a1642f7f4fdd24b4b90c965e30f2160135b2bca9e9d5577847b10c0e6f83c1a89efdf5455b9a93a7e4ad8b3b3a24f540bc5b56e674541d0dace4a2ce9f94dbf1a5349939efe57c6d66f341ecb8d8dd9cb5250c4dc13ec5c84bdfaa3ea1db5ec5c7f54b050977d867bfca93fa54c741c6af17bb84bb127575d7d54e377cf743f2ad0da8f0a01d41ccbb6a23cae83c59ea00d67ed8a3a4fc05bcb69a8a93716f21aebf2cee9e56e7f53aa85cbbaadba5070666a0925335b507963c10f596200c10c4840c0a4af1c84b267bdb19087658efdb0a4c0e54c4640895d99ca189010e7cb097e4432c3fe1964ad8d0870b3ca56a9ced63975b7eea983eaa03c294aebd85cdd4cdcedf79cbc1beda628adaf3a2a4ae3c67552d48ad2b8caaabbaa794c9e0deae9d6573667babaa00d63e99f20ec71b0f44f986ce684a57fca827a4c595ae688617190317ff191cc908cd1b0cd1590097e82f2682cc71f01f04862190332be7e9cf1482652f8940422beab3ca2a08c01a100f6143c9269c12399341ec974953120aeb76fcb23fbae4732731ec9d4b1a183f3a4febea2b47bd3e23c7cc3b98c2f59832c02634f1ae404284f7f8c0cc25327901780915da33cfd1590295040b66d02b29974c8becd20db49893ddba9a1faa90c7bf653e35a0be5e99fc04bae5f3c58916c2aca334579fa6784b1674fddf60f432789f8daa0625db73e7ba9018acb3597f510d72bd96d8668d9575bec57284a43baa284193349a2a64019c3ecaf5a282d852750ac88c1052a43dcccfe7acb19c37615a5d59bdf4cd0797afafef44b455b9e354a0d2fd6ff569f5cd636a458d6fbf8a33516f16db1c84ba9093af5e634c63d39107b3a482dcf91d20488263dd09be7755dd7b9d72c96e74561a1f23c16932b2656df3979b85b1256a42e8f3fba72668d588e62a2e59e94a94dd20b5b9a80714fca449b34d1c2471b542cfffb5976f723ed0deb4834341231cbbaeacd9d8b82f723c5db23112d997bead780c27279b64896939df4ac6fe3405e769203b172ded81951d65f24c27ac9c38d0dc52e36e48ad301d175507ca6db2aca490cb75f00733b0a53f7d0df79ce54bbfd3409b5391047394973fbf9a44d86e87728f4752d41012d2e18c1c18731d0c060f6932b38b840060b245c7069a629e1f770a67b3613d7442ef6f4364e036b6861850cb197b670847d462e4fa40994ece755bec6a9a8d43dfb2a037d75cf15986e09de7f4fc721efcf1598eef721879dc82391e7fdeb52a622ace7284dfc6616d8390450532ccbd3fbfa3d288b3cede5e7f2ec29af3cc179f83fd284beba5e9e7d75a39ea0b9d4b69270d49331b7991240dfcb212ee314c4cb94c9656dfb8fc09aba1bd6d4e6256d2db127d2f5c76181b8c532bdb5dc9601b382b2b7dffb03e45115fb22f8b10e0445b03caad289df9540c0b7e5117dd0f5e203114b155a7c20afb26d94c7a3effaf1cfb6e9e02ac7b2a1288fe78d2f7adfa3917f28926d4579bca7a4d7288ff721d938cae3fd4832e5b16ad2fb1699d454b924dc778eea7a6fc3c97e2568d58d1d2d63ddd357813e9b1ef032d6950a60b37e7d5a9f86a40afe6cb609924a484a48266882648b5401fc7ed35b0c4032592553ef7bb8f74eba8df278df3c5814f88d0d1c2c0afce69670bb50eee11b2421acd0d46aa43780cd224a48a6fdd77fa4eb41727c4b2af57fff22955ce551152020f8475d32e5f9ef7d94714f95cef5967fe883e4478a642ce3eef7e0b7c926fdb02a8d4a63b9f481299647b3223105b0d98f64b6b9f4815944c9551ef5a047af84642a8d25eea80a10f1bb3c32a23cf4736d8edf7fce5acfc28d3674b06c86cfa6e8916df6f8c328e97bf0bdb1016cfa7b4420197e93ac17c95005d2ef471ef5d20726ab8cd552016cfabb5192529bfedda6bfb9428844bd4ecd18c2369d5185eda73f6bf698bdefba92cb0470e7b48ab0d5d1ae635965ddda81eb815d59eb07aec77c74bd6f923e8b6cb34596ccd37dadf5dc8004f07a65734ff70e545f9e07f2b08c7b1470e93793ccd3551e06805e9e12202f654aa3267f48bd6f57292de17d07ba45c5e2935d37c2e9072068a7c5b22e4739f1d05db1e1e528271b7240206014c4a341260c162ce45ca022cc0b5b76487a52861147648872b2b1ae3fe86f5b8913140b7b27169e807125a4b1f32e6377d671998dc57aea1d5dd9b8f6e1818568d95fb8a78e238bc5fa56c8a1e7798deb2f54887ebd61c78d6b1c8ef5f405d33a284a6b0ed4fa1a7e7dcae44049c6af4f9b50a72098c7cabde01ddfd7a72d2fd613eb89f5c47a7acdc8c276b34de3b2fa6ba85c56c15cb39a3585c665d50bf7d4ffc8fed23ef5c3ae7694ba824005c1b44e8959246b0c0bc7ca519efa23c99222596048d6169616d60eec531f24594fdc53bf5382baf5595498a77e051bbc62d5584c2c1bcb897563a1066da01378039f402850cb47f5517d559fd5775563622122ffacec59abbccb63e3b8877ebde21ea806a1b4e0a472532cd49fd377fb9ebc9c3745556575c5200882552cd420682d0cf67f594fdcdddd552cd4b82b16d3152bc947850307787545f551751d548725f47e7c6d5c569f2f9804d65c56ad5c56bbe7eebaf7f83d1285152e0a1f18bd6388fdc428db9dde956e454b2701f754227107d006de401b1805b48136d04673d448417d3807eaa02818a7d124efa028cd55ba5579e2c00196e7ce8ed380bab5ddaa7ed39a17963a9aaff6d53ea6cfe64dfc7b62a119be9b7f515c6492f5de3b91c61f9e574d9d28301ca87ba0d3defaa3c510baf7ca98f7dff3edf18710d0fb367281389755fa447bf8487a73597d17e9564c58500a04e3b2d6923d6d93750ceea9b91a23c1f5af7f952a54a6aab2ba62a1be47f65520e27a5bc64878a960fff56c1eb9ea77a3918b47a319e5a95562202e074e813850cabb7937efc983f2b478384fcacaeacaab794c9ecd73c2e1a47253545535b6b06757036b1d5367eb9cba5bf714d6cb6f7b874eedb212f6b4d70ac9acef22914c5aa3364aab667dea34cbb9e29eeae559ab6e7d727d955dd6537d10b41debe95657d9542c296e7d9d27d63f742b97554a1d0cbfca11c39e6e75ab95d31877e5405c45ab1a65b2f22b16456a39074784bcc3cf1f7e53ddd3327c009027d205c005c07b64ac3cfb7a187cf73110bcefde8825dfbf6be240351cc8c6d7f81e7f906fc3c6534a698defe11890648cc6c79e6ff8cfe38f72a73c53b83b4f922778c90f8128061800594029e591e802f2ec7b41147a7fbe6b62c17bb57a3dfc8837decbf1379e99070f2699c90420fa9ff71b397ef07cc740e8fff99ff2ccf1fd48f7ec6c6d72907e73595f8fe24027d2cd7180283f3e83cbbc9f09588e8a21c9b5f1373e04450b726edcb8f16ca2d06fee0fe9330c916cde085bbc51e5e743931c0a81709943b9ac07eef16fea4bc342dff5076b6092088aa0088ae087e18fd7439b3b9ae4508e9f9ff7f91cef0ce4c1ff3cb3d014a63789fee71bc7ff7c6bfcf19383fcd2744d5cd645416303e0792ecff5df9403310b6df0fea01216e2e0fdc125bd23c70365f0fe601307f2f9b4709af9fedf1a16eac00c0bf5bc9efdf56db13bbe2dede36f3fe42047063edecdbbb98e77dbe07bb877fb13f46e19f8308e7570506e507a2e00640c84fe1c9fa33c3d28a7102555b121c5fe7c8ef7f9f1f969fdf8fc940e440ed2a15cf6437a0f0ee53cfedf0de765acd7e0f40eca1e92c2c03dfe7dbfefe071fc069f43144d72084739854966123d0fd9d707c945ada60cd412cfcf07a03cc15b414f89a33c2d07e5063e658e72830dbcc3e5f8f3755b3eb69f291e9288ec5c6bec47f5a571596b8abd31e5955297ff900348c25d17167bfefc043f5e53973ffd90ddd9ba269d966e8b03ddf80abe0abe823f61f7abe0cfbf9fabdd750af9f96aa3b40fa7a33c419ff2047394e7ebe6c8510ac9f106f80314bdf93e3e45642c4739258407e18b3e3c0029c2ff90df156ec6cff8703c0357448af02190457f00f2fb215d2194df54112902f955e9604fef36a583857ededfbbc1c3a177dcf7f7a23810017e00a506e5edbbf9f8e8d0a1e359e30f1d3e3e0f8e3f7c44d14b08452194473120431fc287501e19a0ec9c288fffd023993e4c20445ff445e5110825d12399e6f7f0fa9483ec3b445e40834944c658609233afeb9edf2d07d9e32ff9c17f374aeb6e37f203d2cfb48fbfce9fa3ce6be003207dca65fef3e210c92611d948a6d8cc1e87fae628dd0cf7d0fb537a8e7bdc24633f4ff4530a21e1c70ffdd0fb182a8f623fcfe6118d37e6d03b18dce365ece7874a108cebf037ff9b2a41dc0f7682fff929bb262ea3e0cfce06060be9f89e3f2fe0f127ecb427782978661a053e0e347ba09fa70d7d8e339ad5a8344dd5554d2b996235220000003315002020100a078462c16028caf358dd0314800c7aa04064421a0aa4812486511003310cc42004100000008620a51852ccd40e112993c39c9f0bbe44fc4afa2d9a460bbd248a960b56f80cdd6a87d2409199d39c0718449cc39f10628e7e354914e56b0a11a84e5071dece2bdb4393a7c664412dab83a3c26e8f97dc439b0492dd05bcf3ee0f2a04a8e53584c5f6896ce22d4dfe929e662e1fa23e98248ae87b22046eb927e450c43c43cdf1438432a69493835d50eb2057d723a32dff72a4733999750eecc989942b035cb9a77ed622ecfb2fa6d3de556b5e561f0cc9181e2dc9f7fbf1dd5748aebe31a2dc87ae31ba84913d5ec9abea65a9a04294b9a3bba5fd83e28ce498ae80f082bfb85b1fdacab4cfff0ab612c0e76989f6b85509a66ba4bc15fcb7d63ef3b6aa640dee6f461cc25376a06b614af63b9cc840f41627ead42de25cfd2aeb983c0a86e177395a92c0a0952769cfed5497b5718291b012a6fc4d7822be931f9cd717d1e60d8ff5badcb38c30941405e05c9878879f415151ba4bc1fee9ea3b4dc206feb73c53cde6b6041c929bd5fb6d46d8a168ef2ba287549344fe48c31d2000407ccb2611e2354d71ca1c3e2e1c57c9212db40db819247440ddaabd3ef80c1f90f064c08ad8bed2fb24bcce93d58e882cc03d3d77e358eff4cc01729d201ad99ea53758255e39f3adcf5229cf4360f02a79024194945a6888647d2807b8dacd1f6225e07b1b128914388baf8408dd85b8821242f0e5414c1659986a4b7fb737791187f945a1853567eaf8b3f81acd428a39f172079fc0ef3356c12a723e1250728066ff8da180c6354ffa654fc4e020c1dc4a33a38631d024082adaeb217de5f4cdd3daa0a34ef7f63b39aede2c65413cc0eced81b8d7c56e1834a01cbfcbc7c8d76ee5a9abb0f1cbbd8f9e3361519f8e50c6e4f5f5d111024de94a320deb592078045b9c55043266db9ffc224273641d283ae4238f5d54080f804b2c13ce5f4f469e8c0f037a8b8cca73c8b8ad3d309af94dd45d6c64d59d29936bfd8f443274e4d10169185b0705cc4564f4a23a905ed484a517959689e32d4c4813d91698442435e704a3bf4a7de65de4d09d2597b07df36c0576ca3f3ca8eeb9955a73357af8b02869a64eb834defb72da29273630c0cdae1d42b68a6118f18d85203b1d3e62a841329292a6b2a7e80fde6001d09f1f8c48d4c665b0fa485a5373e9e6ae25ff4342619658a4a7476587ba9d95c5c9dc39f978cdc7969171ed8e2a46656985bcf8cbf013ff7b54230f1dc0e53fe364a3e0146d318c5255267ef6eb56e1f5e43521fe850c12b12fc41e37e4acd4962e0738a6619cca6229ec04896bb95ae8999970de49fdfdb79486258f703004b5c2024a5e31dd2dee858ed7fdd5747b7fe1c8c4f4d938affd6d224ebd78d93d734dae568bfcb6661f81307c4cd3ffab5952738b6feb4e85496bba8af18170da09daae3cf0c688e73e438dfbb33b5f903b054029a5d4fd39db984614840f55d621d26b08d220c6d6928010d239990b0eac254857203b2792f846f13792f9c3d384d8aafbf05f839c60fe6e51578aae483e55db29661cc34dd9f929651d58d778760245e1391477b705d2c11e80363910f7dd79dbfe94eb6700d38060046b00044bcd025485788470106f445f7760aa0f350bfeea552e11b216fafc413eed55c4d8b981066dc975f3ce174105970b90bbf18bff4481f0e02ec2882cbfe383f00a705852125a045422ad13ec58b8d2f158692dae94287590276cc1611db23762923b7ac7782e9f47bfc5520ba4bb8a5eb2484dea094ec0f0a07733633aa1fb22bbf224f664e1ad361863f29dac4a71ebf00137a2c875c5b6ddcf1ba198a2762aa193ee69b313e23ad95affb7bab461c481173b1901c772392f612b62560273044495467ece8c04924462a02fde614c4ecfa02793df8f501d34e4583b63ab8a6bdc596453c4bd4032f28533b548c18838629d0e183073496a6d575ba65f9db4cda5d3b31858ee5139d25c82d17446ca06772e4c5f9c612ff9df5446837d5c9b9fc72761d93e4aaf8fe41e823a3d258bcac0c2a7f63e42af41c1ad0a94dd38d1f6a6f5c8710863f30d0ad6ab50aecc163a92ce888c27f29e4b1f8049e907d058703d467251fbb0357175f92eae656ac301e9925803c5c3ca945fe25edbad3829924a9de92bdc20b2d9eec3c5df3b21d18a5f729211ada1cc54712289227cb4dcfdfe7e87a71d9f57bd16e059297c42c012a690ca7d95630d6459c2eb5480c6d16fa317d6e3e65c064004ec6b109c070f0ca606fbc5c2f8a1e5731d75aaf3e4468908b4443de32676c0b03d3c4a26d71cfed66f76427a034541da17620d9c6ef538f8ca4054659a77f5b04a59c22325b3d8541f77dd799314ee13ec754a0182118dd299b2b34b74046897a07541aac1d6aa825f0628dcbd2bd616ad89314574d030445019178e391bfa94379bac49b83a12759083bbebf80537a1a9ee6dd293995d024fe4001fd0dc67e6bf89ca5d0b975bacb6df714829191648322003e546a48b08860cbc82760d32127e7bad105a67eb4ce820e96b2189bf79dfa590c0c9f5b062656a279cef34590e27493c1382fe66de81ab7910e5354c3da106a4ce2cad0508b4f6fba14b9ed7f0929a10c87f3c0f2d4708c33cc145fb450814cc52cc8dfd2acac2d404d03624c5c76fc95843e817424a654eb8860222403c0fb9d2783a4d97af105a568ce64baa92fd7b067a4b0f7d1c5433670b281256e4b7fa8a1c1eaa0d371643e4687b74bb5cd964acf1a0a89028b3b5a9bf0c67a0e6285cf1a5cdca779ee62f35d187e8f8716a5795dcb968cc16c97030dd129c02bee0182e215b5b0b27577cae6d1ce4c5a570330cca870d635fb8eefa757dd6d2fee45701d0121367a86e1badb001d77e5b66e06ea36926a5d30c8f3cc2c6aa8b5ae6799ff40d9b9811850a015537705ccf8d68425c8e7a7670da20e254c833f8c02710574c6f4e51c7ee7e878ff03a1433b2788920f4ea88c6420fb33161cfd53106b792c16eb850ec10f40c15e864355fdf07fb12c0b26f4af0958a3b3a43e42291539cd920e7378a4a06df5d46dd372f74a168f114766c114e89ac332ae2e50b6c4e8c0459ce0c9e530bc12b5a80e4a666b007184e06d9ac30d61eca9a1193766e0e6664dbf6f0d8a4fb54e0f490d2cc77b07671d3fd53c340ac44aa416684ade069c4f9a73d62d22fbefd8b1d3a2f2415c68af98be95b649f101920a9d8ae9bc1b078a8faf079ce361bf9c28d4ffc6878ea1407615a081cf483139aaccc70d6ec86b5dd913ea503c299dadc187fe306decde962c67cae489b55abd69ac69c0d5518f6315c6ad3ccfb6ba6eb60bc54384febf516484bc58fcb0e33c8464bd551eb991bcfe7372da76286fe689cbca1c0e299786109c25f04e6897528ce4b1f1e23d6ec69698f92d63a188ad5a9146605cf0013d89c0e41243347651bccb95630478fd79c0c63e440001f00f0d1d16041b19be1b81042d7fab31a39ab985689fe6eb6d1a39fa0e08d54b83e049da62cdb03e35e7971d42489fbe160ec7c3c2dff39460b282f20874e7c689846e9a21969455df6a66cd685665a84f8d8124af0dda415c0d504aeb7fec9e954b94f599631bd5f26c05433569d3ba5226fae972c1bd8adcb258e59d2dbcfb6ac863de1163b2afb46d5e2bdb742777eda03e84277427819f7c4582cbdfdec140a113b903b2a1fd1fb5313bac62c52b14082eff8e488391ec74680232f021a6847753fb4f1b924308a6625695f5e7dbc0b272c012a155fd6aa62acbe18f2da59793fe8d75887aab5447f3d3cce3098790c8c46b418868a0a248f2a1653ea86df27dd13bf4b95d9fb9c6d47ad29be55c6449918db4c575852d6f26cfcb37f29d85081edb025e41e455f106dcfbaf8bcb729cc7b4a0565c6baa99b756af55ddb51a9d99b2b2b42e55dcf77bc3030306dbdfaf15a6d9a68d0e63c19415adde41290aa165e0384d010a03a79a00e87c8067d579c0958d1f0b49aac87a7c7321c5c96ddd8b7c7cb1f6c2402d2a207ee1533ae16acfcb6c6600ff6b43ffdf495b3280c5b749f56882110169d807cb7f1d4c9eb4ae7df6f147508cfce7a5d947599854597e57b1ac05d680c3304b42ec3f7ac42fbeb7e95d97fb2ea8ad4ca5f0a1836f7b2658f08586e5c4657846d067451a058d4f75d889c7b81ce4418d485f83dc812cd3a9c8f27e0a3cc4ba3a74aefea1b6f6f82f16aeae47b2f7fdddcad97175e9c3b41eca650d3796425b55fbd9e66df5761cd307e3b50d3c3c867945a372213d84dd40f413597226c29993c4b89c34a8d42f518108c09fbf7021db046349a7683e2db53dcc01523329d1272cd1bc38549bcd6edfd8060c81c7f8e87fd2c5f4c74aaa4ef6b904bed78f17938601f89cbdd8a0d9b6688c0af3b03dc286b2b49a6bccab41cdd1705fe43087150835b39eabde1786a19fbf0bb82ff233ec72a8c23b2b3068ee8a7ea74d4fa81deda436020a3b72c66055fd6852e7dd2bc813f956961ca66b6699956d8a31f8a6fa83cb00039debd81207c352923ec5bc56e4a508cb8f179da60a3668ad025f949f07dcfd79475803650e281eef3a15bb56bf961f7aa4666a525a3394daf61076ed866d13c553fc31f1410473e4696b86cb884c3a78dff2f638e7e628e8309dc7cf243c60334b609a51d8e91476ec24fb7457041e71e4479ebd3ee2b50ac8f361ca025e02027e69127c59736c9db7c0fdd39d26e3893372b84dd929fed86f82a0a035bccd0474c52441c99f81ed04b591a52794122ee6462e676ef4d3a681d0bd16a4dabac3457c3cd0e1179f18fd41a5330afc642133cdae8ee8d7d2d5bf5be4d3d0970d4a80de81e495fa93673cd76e0ac245fd5320019a4cd5b7bf0d488346f4dc23a44c046ad3549e2941c679d6eba91233e91336bd3cbbb084bcd485c392419c4bfcb69d39c04c19d60ac381a01efcbc3042e0f85150f2146b5d09ba9179344c7cdb876a6fe608aa4c62603efba4757f2288de355fc28e7761f85f404ce4343848ecc3fb931ff07df80f121ee27969a96fee24ed4650acba702788416dde88e2ce710acceeec8dbaca49462f0af94f8df067247140520ec8021ad170bb17bf39e6ac852e8921172e3ac7056cdae4116680a72ad74702eabb073ba285b41d9ab95be7ba9cf0ee65675213d436cdbc610a3fdf1e0bc744978484720041615daa20c3c353f0c93309203eb67c7346a9fc7bfe7cf15e167fc2e1547a2ccd0fd01f438be01401c11a0af13d656585e59e981c761656421e40d3d05a28924d31a6e3643d25001611b86953450ea445b80ce2d053059ad7b523bbd93a5ba6d0bec8a97bc5d6bf7bd79eff34f7ebb0f22f554a4f73944d977daeb76336686ad4f2ac8f63fc0a323a2f7fcf55ff5059665638d8848cc7a274a7263afde71bf8de098b2967397056a3d662590949cdc994cdc255776ee4031ad2aaed428bf923bb21c55e11516b44874e4deca4e3e21e0d36f7f9280b971cc298db16127c6a859db252fa77910f47f49f64addd8121a0b7698b1baebb389d89ff99ee1643667b2a14c6080d4119f95c24c730f6595cba6af4254bf19df4843acee2c97739e97c0cf4315306f55ef0f18c887401359bb7836db8f071cc4f4b339da7c19c388f160e6f1e0c71a51cc25559fe6883aad6a55e1db9c1c4cc191a1167c83bc68dc9401074372e9c80fab50f89bbdd560e8ad225fc8ebc85328654c571125b06ff2551cf292ba2cabfc6809cf1da85a71317044a1498f42ec827ad20b92dc5b33a2a48f1b8c831628c7721a74ffdc94bc7ed01751c29e8f393d5545e2cb61353af84cdb9544cf67ac4ad290c60c08a491c94a25f2d270e84943dc0c387de7fc897991db18918e4c45e4645dc67ececbc4c4e5584c9db3d9152e9d600c9cc78912703176b42e2a0f93ed05cbb7172d190aa2a5e3eac5146f85c430843fffc06235e671ccd6377809e40920fca18c74e45400ddf37a7b03ef1d786b3c51a297d0ed052b210cb50f4dcc4fb0eb27fa0cc0718f91b10d0fbc584c8e2bcfb5fd00e81f8c8d82fda6a4bc359990e43427d76214edd3a144fcb57eb7e65c6e4c379287e4ce2fa024b3fae6e275fd0c75318986c7362262b1c8b4be9697ed1c344c5eabf31f65c0e83f01b8e93dedb624105b4ba019186cccf439e4c87d50ef4d12d21ecc96e0d841b97bc51e0382870efd01970f88cc6718c4a22977424413ee9158528ab955cbb187e6999940479e12ac4eb183c9e27d6bf00257c43e7fab759d47d47c408d1a3529716087bd9147ee115b797b4ac35baf0d8a89200f3ee1b4f9233c99c593b20d7b2507a8868d3ef09d2573132f59ce91033179f23a447b4693dcd0e14a3d6bd578151e6b4fff54cc52e39bd7f0304685d64a629342472f51ee040d2f6583dca2cca3ee9ef628a4f1d00caf6afcd66808ea7d3cefd9f0cbcc83384ecac5847d6b92e5fc928a5e29574c46c6cb5508a77814aa840470b858460986f23c42b1755989f6becf82cd4f7304db2c9d475234829eb7ac41ee34f6acad02f0f6f3dfc90f0d8980294c9a7e9bc59f3461aac7470b3de53955ff7f796db4749d044c6938acb0c63c6d81c93d1ce311d85b28e321be576a07380e4c4c95fb72b58aedf7f36b701c9a84aa704974bb741f1ad5f972a9f13bba5743e40c8608107bd50030c9186645e9767d022743c5eb8d273975d8e9f425a6bf3a3a5375832a9b5b49a07263d83e46a5a6c84843a5665e567d5300dccf6e84ff454eacdb879f327ebf51ee88f8df5c7527a2b9f2049bc1d131f8456d13b63a9ec40e6927408eb5e3d3b3e8426d0baf0af4f5059dd79c6b672792ba3cf63bc26f98fc8f8f8610d86f05e165af0d2de02ad9d1fa919ee23e6ff7f42b1df8d61259aa91b581e451a702498f22d5e93e2c290b384bb3dfa647dc21f3c81d57e0c6e979a9118c9bd7503ddaf01be085a59a42e21684f0553282702db8c49ce54b49f52434c7520eb51a82ecba529c33407866b5c018efecd52b7584eff569cf0c1aa81cba5c73b0e8bd28bc9012a800a424fda8d2528a306a2f6513ed94657a890da33e42ddca7192b0ba3dd28cf791c93cfa8c92a4ea557ad0e0b241e9538ccb2a4ea6bae47fb667b636da163050faed22dc2c5091c01a3d1440ced9744b431de139b5be196239ba6e3838060f4dbf8f237283dffce9d47a60872afb58258f921e4bc219b2e1508aa43ab33d5afbb9e4abb6af63091d002a0e28561c3e30ea314a7f89b54b67143538e4556c89306152ea679ac5a01951001e521b640a356b5658b21229357f0707a4853aedf8919971ccc76f6cd4a6544c48c401de79a0a722e44c744913528342799687336206c491aed40b640986c7c8f2243469e8d1941ef5b7488f5503083a7b6943ebb29fa292577ab7c4c15b05718628152e664dee43cc97411d8b76bdf0015bb296d1291d1f244fe4892df5ab4ff1a49441dcf79218362509380e07b931a848a60038ebea50ab486a3f1aeda25ad200036efa5573c1c63577e6545686a55a4c69677b23f0ed7c1233e35e13ae9b6d4e51a9bb8b7096d9efdb4df2468d8ea21cdfe192e6f0f4a9213aed8825d5f22dceeacc3bb5f4c6b9c57970ef8b98cce285834e0e2360876c810bcc62c69682528b130c425804fe0cc8c2fc0d83f34802be2803437cdeaf7a7e7867958a184a9a3beb2ad20de49f746df4c9aac6af5b2c399e874c2b81439f95a10ae32ceb54dd4011df2e5b6d9feed19d117c6c3bf2d9f2d9fed6d5cb70fc79b19c82f88940ffb65610b65364a0a2cd570931884f59de7fb6a5ae1998f073d76b76f00fa24a497d2f009ad4a51a606384f4521b632638684882334947e21e74f1eae23aa691834088df7a1a840891fc0c88c4291c99de2a42267e8fd40a182f8601dca7e83eed82e83ea23b8558c4d86bae9973dd6861c0995f602583206d4aae609d2a3163b0894008e112a7281ae5837101c4e106a75e51db9dbb7446b8ac060fcfc03b3dea55ef830af6d074cf4d93b9bdd6df48c7d0323d804ea8d073636d3b290f4276285a89db545de4f4093235d3a945bda5c2f5dd98ab66cbe9b0818990ef5661e31a943bcde4fd27a83432eca02525c45aca0f7fdb95d719151466b38012444f72b0da90a5e053391018f91b6d8466edec407bacc63225c058d2d6cd48d876d0acb058ffd501407a9682f376ec946fdb7aa907443b12d515c9441b950de668847ee494ae81464b47cab53d451c048623c04fb62250802bec842582b7ba541a80520315168ce6295a496d74962d38ed2592126ba6841326424ea157bfc37a98f08b94320e977efc517cfeb2bc4cf226eb32fcb66587819183ddbd2acefc3727abb6eaf9b5cf2914e448e0cd0be5b5203d2e60024ef62168478f3507dbe40eb8edcea5e9053f52d34221fbdd26fc213136d9a55407c22a60a38a0900bdc72f614cb49a977df524079687c4daa3d8019173f77ba18a1fedaeca5ee0f9d06b2da5636e8135fa6a592e2693b27a6c1eb732ad8a866e3a703cf31ce96949d17481a98837f52828f0562b62a2c9556b3cdc7bf2c8cb3c9b5d30d7104442aacefb269f5c11dd98463ba5358d71623bb3fd2718acd652146e8dcfd18818415be062d92be9e57dff56fbfa974161dde38d982b53652bffff53b4a8e2c963e986599ba369fbae8f09ebf7cc06043ea2ff35f7fe0b26f67f70f508132f3a5076f1ede564dfa562c04dace3b97ebc2b6bda9495864ed3e4c730180a67207f47761f5a0420f1765efa98b1dc8e5ed746acc307119dd6d4055cae1067c88aa207f2d8350d8e048add61a8b5512c2fd78570d9cb8010eb1842625343245649ff3ebbf88627677d10200c6ec166cb5e6f5a9a490e4c7050b86db8b2d88a24e9862c18b48dd6702b569e769b2a896a2378ef8f85a043da11106e13773cfc494bb0ee04660d8f2cb759f103c78e38d2658d6e0cfe6427d018436e714aee83c7bc0bd7ce72d9e664fae0e42b6729dfcff3d09b3e8052b484513be570ab8cdfcf1e1aa1f801e7c68950a9858e0aff0255ea56a65b2d9900840218838aec0e84482ac72ca885e8984d7beff43b2e7f7e688bfaee158b6c358e55ef4c94e01e41c3da04210e086bc788d6e32aa6faadccbd2ec7df4a604e5291d1b4a7f547b3317a9c9ffa79882cbdc80d2e0a4fc917f15873c5c02520545f391257aec6d7633bed31a537cab090768bd5212f7f36764be5501b691cc7b332766a3403f22cc1df66eaedd03c7447c8958b0ba809c835843eea17195d3330fa0e99a34851211ab32e8d554d0467612f02b2561907b567cc50aecca65f6533f2215eddfde36b9571e533ea249a88ed2dfa4930e38a056610ec675f50cb02a8e8ba58ac882ddd2d980d909d6ac6a145c6ab15e0a618acf9c91e8d1dee2efb6187e555cf327d6ad11c125f3b8f3bec092f3132ec312ff8eb2a447ed026644df816c054b95631a61e0a29bab94e29417d44e4115eae80aac4edc8e9866c7c23ecd1e0c064d071122900a9c2383f5414a598811914cd771a493ae54b43a8984ca09dc08c2bf52f99a705f2225b73378afcf91795d35d89581718e93a4bc7d60f2e9a000b60d97761838bb11fb0b9a518546f58abe037431e54df37f317f381baaa28a2baaef2df32fc2e036bead09839ab7037880b6e00c35918e590eeadd3946d50f969904af22f76944c161e057af6a7103d1c3740cd804688a9df3afb75829a28405c027935f11f960c7bc5f4ecd1e561d383b79eba369f88c917b4ddadf5a9706183550a48151aee349f3f97fa00d3750433aff1d38144440a87df36f6569e8d2a120e19708fbee901ca97de340b62c5af106b7b2902347518583d893d2b4b06649f88e58ac4f0e88cf55684e5701f799b46d37134cc8561211a91eb817eaf6379d1a33f9018f36faaefed74388480f4cd40fbe06b534dc2dbd29c516ae6aa5f867430fc27056859c05aa812d5a3072e9e70c9e6c1737e9a471c9ae2d10ef1f0c2c8a5e2847d1a3901a0e7f47e9e917a3fe98e0f145bbfad9529ca83f19b880337ae50de99d3ca54cd2804da4de880f9668e8398f2d1e4816c70787844c72377709dac922e2989f2a41695fc54fed030a6397e09aaca878f8171dd2a454ac455ca29fd108181003b79193c3d21bc305f9fdeb26d82734ca3e191fc55bad8e65230f512e1e91668cda4982bb1c9bf74d7d5a5505913d4005c771adabfd677491823dab24506dc28e6e60356f9ee17d0684ad89115a881b8c46c39e37d78261c4c9f1ea96fb7e240a8ffdc11972179b774bdea980cab35d6dd2b8eb9fdead014faa033efffffadcd3899b36b498a1cbd46c8b0b6555a93df391aff08979f8d84553eeceefc16d8324d672845ede78c37b2af17ef28deb098241349f9f286b245544ed8eb0bce502e7eafe3e25f5f6dbccf463778640decafe8037c8c32b8ddc36259b33e7849b6434e0f7e6ad1809a438f0320920a008e3f564934248ff2e076df4997454bb76ffd5ab4be0148618d36ba0bf082cb46dfd8a3af35b6485373701b53c43a6edbecf69514c4d6030097e3c04a14050bdc4ffedaeffd124c5aee8708cdf4e1cf14a399ba2da3d4f3a2db0b79b856f71ce810e8a158244381e6770aa43fa939ab577384373730c7e8f39578788c95080227eca3d88a9d18996ebfe00fdcbffc26c2d328e8661848949260bbe58a11569af2679ec5893d1603ae96a56d7df3e41ec31d470d0af9c2945a31fc02d7de0ca87b31c73aab6b399479ba2ec68405bc9a96b87828263913b75dac75dcdf8e4e81b38d91a1e79bc55b6d7813c0fba5152cdb26d633c49ca6085e860f54705e6f48dbe4456e471fa68c02c418195013d32c580d083b640a1075c57f1ea27726d94b09fef5a29408e53103b362f6b8936439fe5d0cac498bcc8ab88606b4a93bd920b66bb06d002829c3e2a8972179dce4b0920609d742e180d902047fb7224634d74d959d603b7a8d85844cf700741b4bf388294cc1c96836854cda31844e57b4bad0f578344220e85863d2864c2aafbcacb454a2c6845cb1a820a65b13374dd18efdf934ea87eeb0d98d0b4d6d7bd1b15e7a0762bc74e0ca51156ae11502dbfda8a0a85130910e72693ca9ba763b12223cdd82e3ce5b4e9c1744451458df7bb699d80be7ccff27c9a1dbe8c5e95db6f08a04949efe1a20e34ae4167584079fe935262c4d13a59a15d8306366bd41481fdd36e241353ae9766a0f32722e773af6ff36f1ccc4ec7bf35894abbb4d80222bf191fe1cdca660b697ecb04700db84d1f9e806c110c5dd606396564b7a49e536cb004c367006fa5eeefe8a3d50a090dd0abfcda32b35d81a23bd732960f74fe4801adbfd6d9727cb9cd4607f0c11a1b0a2d2d2ef84c22e110bd4bb1ec6b1f192df289dccf0c35c468eba7a3069cb4631a92937690c32050216b50f86a0f364d35d6028a04b4408207d4294569c81b5f514164821fe2043f458aa26659d5bd367291a60208bf5a9668bf704d94e1d74ebb93479f3a366f936a1a8de83158ab8431a62c9072a73d2da93be811dd33bc1c83a51796fff295f316c85a5059b574e962d2c64e0dd3b8034e2f7c6b1fecb79d1941c5e6b33cf0657801f8c6fb340d1cc1a5e7d8f45a96ca42d3d5e5ddd540b70a9972f724380d017785889a76a58098534001ee31edb2495863f4d51302a42c7239ba2b0237da183e6da316cae4cc64ad01b67e2a8acdd389caf9a0b110f1d775bd3a88f46865e96e1b89c4be1f3f68c12347946c2a9c6b13fc0d957d44f62520e239297fbeb3d69aa049daea67c88eb6752980aaeb58adf0c2cdac2596d4dac71780290e84fe89ca9ffb07591514430e54fd56bdeabfaa37542db772c7d778230928e87c0de503b0df4afbda6c848cf8eeb4efeea25509232bef74e4a02a052151a1de6cfd8efb76f3369b2f7c4f9188ae4052428bec6cd62fd724921d3cfb8cff84c2ce479b04812cbaf0d47b006fc1b50e03e24c1a362d413eca8462fbf4a676f3ce2f3ec6a74b2450ee87e09c4ffface4a4e16b02ecdcb3f8dbe83ee328091f8685003534733af57c469d35c37af5049422a05ac96fe7138d5dced5a1807392d099846d114893fcfdf32fd468090b45e7f8b33255335a86a950db21a0e9c41382ef92c42b5883274248148c42e1b26974f35a1b260bfd931b27277ac9662c4f52747e6ee5f931f4dc6eb6f94473d028f7a99b42da7891c68edc1f3066d3861e955388a08dca9aa5f347da0670f6e6b5751d490902f0870ec3b89b8c2efd5f6fd5099449a992ad2f5083a806ffe72482418d12f5f0fb94ea67842628e8b8eedf6540edde1f12ed197a9244759ba2bdf55c7ac312abe131330f6774fb665da5750553523045f1d8d6b8340cd16344f4e86bbc8f4ac616529d35e6ad08b864a396f4eefab556155e2a293a575631a10241b89218ddf7d4773f1536ced8f11de9433573b03e307a6b238a0eb3ca11eb9fa8a4e370646c1264267a262cb0c053962420e58d7aaab5f65f4c50ea0c5866ea6f260039d0a42d29f50ecb611c7e41775c552f401b560b750e3cb208e9547e2e33e29adf916ece95791a9b9c2b74c95c1e001bafa3acf68c7949d1122b6ab2f3ed8f1c644084e00b2546392756001140d7dd6743e06b547661508d103a580b7abac1b57324f587313a0579984ab62e55626b9ace2906f10e143c4fac581238cda4936c5eab917218d58240899111cde565d9e445e073a1b0f6716b260a3d8c5eefb634995992e399cc3517e390b0029ec5359274103fb0b53370997a7a09f7e2455a623bbeae00e40daf80ad3b2a819508ef32153e24c0debe064052cc946c63c1ebe8f98960eb18e21a6997a5add5211599252ca43d08adbef2a2f8aca3242549effff1d91d2f20693425049cbb872c6feb4683b16fcfcf4141f191f795906333fea9ce0b3dd0cbcf4fea2b17dc87809631e7e00f0cc151dada0813c182f003bd8300741d57194bd8c32a11f55a3ed3e4b3cce6dfa1b0c26b7e3b6a8805855740100fe01e791cdf21ae0a4b825ca536723c2a1b38754aa52d5226e03a3717bdfe8ee9a1aef30bf9a7d5a9bb1731dc95e7963998483782010bac1ad3abbfe0639317dcae0d5e97a9f89f9734fb816876458be2ae48efeb79fde86c0c6329940ae5359dfacc8bfe635424968e7facea5a600ae724c88bb8bde35566ece99abde3b7e2a6fb0f03626a7babd10dba158f160d6f940bf45950601bc479904c7067dd0a7c9d894031dcfb77ea878d8349e5ef670dc4d7f7b73941b970e3c09d05c0aa01888846381af659cb3f9d5e3567b2b67e9b156a6c2bbb09f06fb7007473c241aa8c1d0e54833fb54a610af051854d5ffa7881e9295e44a8842b690118638355b3d7cd37e7e8844055b276ea8aa762337288e379441e099cd57bd5c9171c47ba058d78a07e86d88c8547224f05529164ab7ba4cf276a0c0d7d9d21d9f0bc5fe161a38f9c7e32acfb93c439b8cff7d2dd302ab95e5012544a84d002f33e0e126da62825c231f30ae929789260ec5188b58b1f17df3114999203c851c4628a57f9ea2b54270daef6d441534ebf0c59c0fa18ff2cd114929bb417f284303c68b5fd19067dc6572702265d67a7673084546c21293175058f4789573d1e3a0406912a2145b58549516691d2f560ecd4ac11b83037aa45a9e7e9881783870c249717c7cc8475fab5a33ae6c96f09372266207d7a0aaa0fe417164d95e1e8b5b7fe58f10aa2d7bc053e7a8975368646cd9a753226518f899712596e1052c7e165afc08ff6e66d7f8646ff5a78c8250e95301731db07fc44f95715dcf71fc190f30b0e04dcd5eedd660056e122139481295e93675591d5f656ed7c9218bd436b95912b16915320e54deed63bf832b66cda568f5fcb0d6870087e7fa528c3d2187f897192303ed472475ea1723cbef9f656040018033d7e2d30ded3d070a2a142223675704b80c3a99b357544c036bcbeab6586b07190d2b786c0180a8ff2c79e8d50beb7d1bc757303767882467435d8e8f6d38d1f85807fd67e6f282dc6153590dc182b3a27809e99f7f56f220cac176aa6c36ad42c6a10eb1179d5489dc75116d44ca7e715477ade01b9f311552d415261b4445c8c4ea7a6c2e05372d9986c6622d13a5bfda293db91041280cf1a42536beb67e142c6592accd37194cdeaf5ee7d066743d83ecc7156d03e82b6455d330204f1ba844cf44dfa1cb96c47d8a121360802bac6fb47d9da3b35c539b922c0dacc6c03aab2634fa23b62c042930133cf3744478b6608d304240850151433bc44c35f966e70a7c5beb5ba59581cd5e42cb013e3fd65f9c8e847b81164ccbdd9d2bdb90ebf08b2afe07bb50dd01fe04c705c40f31c805e0d702bd83854d1aef05a298aaaa313cfe7a1166b4f7c908378be3b20ac42a24f74a8d9089e4631b6a2d606beca264a20fb479b026cb5b4d17f4a83c9644487e752228a88411b5685193a7f30c24a0e3b9093c9e6b942f810fb2f9a2962d42116ed1b62a91e7ca5948741376d91f3019fe642e1e628541ecd51b7a2181e154cfbfe5c1f08310c93ff3ea6aab6fa2877e70ae6cf935a51f40a7ca5c1afb8a16ffc3ebc87f5e5cc3955a75f2734080e9d9f7783d0e1fa8f0a9f9fd7e8c0ca1ec0ce2668eb29a29ce118cf77d4bc8e5010d4471002b63a82be3131348ef702565075704c15a02e740ab588240b86c63b6a381ba591c7404155b05d0ea4ef54ab9b7246a3584749e51417915df3fbe1e8b65e9b6488c5bf15914199a4f353c9fbc00dd296a0d724ab2c5e79e646aaad627d804960742ab0c67d7f131c9e5d91987ea69481605fd5d0b578c738d189b688b5d51b0960e18291fa8d71ffbe8c40ed65c8b59071d720a05144ccc45a62b1b9cf95533cd47341180164d827338156ad9d39fdf1d949cf2188dd0bb8774a97563aef3baae5ef25c07c902ac03280cafcbb7f472ea8ec3e0e663d5a75dc27a554468d75c7beb9424088f7d7d44bbd72978b93922f40e753d9002d5d5c437e3e66dfdf7e6ad998b978fb014287916b4fe747504c15d6d67a8005c2f9e425febe945e816eac723429440745af919861a6b3b8a5a1305cc55087b7906ea718ba8ce93eed4674637115d73c231f88a1e914363c869b243acde899a06e48da3fa06ec09be9c5d218982772849a35db913700cd393995fd8e47f6ca0d233d6fdbff5831d065efc11afa4343faf46a009145f2e42cf4265109f21bc2d856002a14e329408da348771c2a317220472d741c4ebe899404c8172a49abaf41edbc343e61a84628f59cac0df28ff52f9a1bb028e4a61b4a09ced008d9d2009420909bc195b44342f29dd7db5e516bf81d7d268d190f6245e8483cc7c67303b7cdf8ec66a914d1b0c7edd268b6bb79e491e657a71db027ae8120825157ce4f2290274ef9ec77cd71514092cd06a8b89ef471827b8fc6f2db67146868dcd3ebc2ed35b71d36967faaeede353aabbe7bbfd6fea1d3a3665feaee16c3e6d629fc62c43b7de5b4fd340b1951e3a74e44a2619dbab58c577cb4ab66ac8ad59978fdd3597feec98739535138761b70aa75db868017d547ba4220d4da26f859a68c33f1e0f4bac61f61b9fa574e47f7cb93358fe27d777980905867cc01ff53641963e8138fd8f65a292f3b21d5d82a88c3013c0949b909014fca9e659e74bf975e6316567d9393e86e344dc8c877fb379e0132fe23b9e8b09cf4bd42b086c962da07c31729a3618dbd49a998d10aa5d873878dcd7c79de3ac8dfc35313d193335c960d5e75737a8a372c7bed51dd9881815e48bd1167667ad787f3432f5afc0b35271c2773c2dae69cc5aeabd3f88cf869dd084801876766a783d2895315190a1a173a6b0ba36ac967ebc6fdac76947b79f8c22246c0b214f13003aafee9d2dfec6d8f83b76421935a8625d11f6066056d3ecdd020f459d074b21e9237824084218abbb595913e2a7b2823b24329dc3b6162abd360b4a39d2c5a6066dc8b930d6618ecb817e39aa6ae1f5cf96ffff7dab1d3d41d3cb434e459c08a7d631d586ab55f58c5228053ce7276ebee9a81fae140d1f8f930dd734c7830a08a1545556bae17c406223a359e0593ba3908d9c8a114a2f7792d84d0941903a9513213257ffab793d8b111bf1d19a7218063c73a192c37b1d7b5b6f78a60ab636dc3517d3d47e83ef45fa8f5a07f27b2d34503a206c407af41f8302ca095f8daf2482c203ce1d4fe41d73ea9d585cb27feaad21d04f06cee15f82be6f1ef2c2c995b4f82c53b4411454e47aa4f7c54b858e72ffce3034b4ec185ba81415c234ce2e75b1c399fa711be0bec08cef1f34701b8dc00f150e4e9af418fe02f64fb880de51e230b5bdc78bd49da0c551c7462f936a7116817a4559f77c8fa25020322dcf9176b42ab1dd20e8245c76e02bf33369b8cc7c6748ad607c7c4ff403bdad3e735811486aa1a75c1455822826886e16f0215830cbbd1e962fd5676b1d97ef38220113c99ea2badae87e442bbfb00e725d273ee99aaec4cded2b4154938ab168876e0d8ec0dd6d4e752d8b099b37fe4d4dbaaec11851e15673b76e50814a868633b60bda89e7517f7768178ca159f863545a66d441ae3afd3e0ecf9333c9d1cec5729de2dbd6d5acb20804872d00daf3be3f89d207c0bdd3d9300555398575f3188f201c149ece9f2ac57a700fdc72dc72c09976cb10ad06b7fcec8bfec6148c23bfa60e13fdde50f46e20851d613a6c620d277ff778918e7ca39bfe14ee3739191f6e32ff819de2102fdf9b60b3d1fa83bf186e6c21e8c4df30a050357e34a9953d687e2fd1c4d64ce8c16bfa02f0fab8700395170e78f7fe6b3cfe1adb0530c61f5bf192af1f72b648cbb2eb2f2792ee797c16a4e76fd2c1552440167671e57f1c697cec5aede778a079f730da94fb7a2a5e523efda3639f03e68f853fdec006c65169e8d51cf87200503073595b1865fad66c2d14f99a7e4bb956a9570eeca60ebd0878624c91d8579b36fbe581b81bedc1035163a8cfc73d015f1a9c351817906ae650cc3a39459e7c7ae1f6135f06844972d134e00d62d821ec4ee1783bf863dff882484a85b4d65fdc5f738a6e51a3b0d136af92bd28d09e9c34eb45e8b4fd30af77f3e33c83d979b39e6c7a1c71c66e84364283cd1c49c8eb32c956fa156e5a0e4f83094211aeb7e8968ae213ff84d0fe491c02c30f0918c9bedea331cac654193907a2d7189ef27ce74aa9f5727f827a02f8b824474bae35351094f05849e16239189d236939e9ee31c678a901a0fc02fda2a07b5d5a189d639a3169042944e0bf93fd2699ca09a138472d91d5ae2fb54863212d65708f2a9286d25df146ca22c214c2bb3f1a77b96237f9cfddcb20c46dd808ff01f02c7bc2cfdf16b2fb8306ef2cc0200dfa60e314e8bf28a4531f36ee022569b426df05d474b4571e11a07a40a273bb8060a95d5ab3c596eb125744dadd46b599d86d632495b8a0db74900c40089e18052f9af4de51112d8398ad2cd96bb854eba1526543b323f1744dd9905cf330824ac35d8b02c5f4e5d55bc52901d06891b672bd19ce4680547fde68bd37f730b30f7ea50341963c8b4b71de9aedeb6988ae2d1f7e6faedf8189beb904770a0ff2ec0722d359d452f090ff5fca083ed44ec14bfab57027a409bfdfbc74f99af7b20851b6c2a2daafb62245a9032e0f182de09adcc133cea24981beb69d82fd1735e9e8dbaa11482ca21a9c84d9e90d53ff27bd14456ec23307bbd18a2b26ad4efbbaf64ae0d7b8c9e7d90ba8cfe51360faf5f556f97a064859694d784006c88bc197bc0cfe912825f025c8e7a430468caf1bff59162876427af0f0e76530307d2720ee61658d4ae4b8b5216632140543e5bb0b6222d88b1d0ecfb59a93f1fcad8648a4895754978df666c4d7e3849d85ad9059fdd4a8ce020ac04d5d36e6f3bf1e5850234d648dd68a50e966c5375f367dd644c40b0e81c42c273fafffb7b3b7476e8cbf96dbac1ef083fbe212133ad5f56705b6a3edca4872ba9d595b083f2ccfef124ad527bec2d796c75ec537c361cec57a9db95540877cd45fb5046e90ac97b9be48eb2c4b5eb8ede3d28bcb4ca476cd00c44d848431c89ae1b44c4e9732b44fe8639fd1a95c50a50c6912c81dc80aba2d99eb700d407729e4860a39e7119a20e70d00192b527b05ffe7ad7995e92c27371eff59917a4c5b9e00cccdbed97958092a212a01971b243258440074c964cd87ccc489566b5b2d71fd40a674052063022d0c3c65f50dbde81a08cd7fb5b6ce9212c6405318267cf6147244f7b8a66a6c4ec708b4b0598648ec54b722a42cdf5bc0d567ff2b120dd4d33c958159ef190dae2e992c8ce3f3e1797e17b40a0c9d7e74c6615dcccc499da60ee7766d02989526f6e95fc0c8a1ba8e731291744651ed9c26aa8146d619d664e4d6f438acf9ef5b1cd609b03146d5005fe237da726401a65d4e9b62d1852491b2dfa81c6ed5eff7d3ef6c221d92778df050b94d10433a1fee1a2074d089200542c5ff4624363f70c1811d22e77e8e41d0f2c7fc57226d0a229171732321b0889c7b6b1269453897100cfb3451a43805abc498e2f572615e959c96e6e666ebd21ad18323417c99e6dfd1e65caed16cc8d7b6f91ec64c2f250e9ceeb6af19b9376474fa56f71c9bccd8ed8e9f626635d3d4ec32b3463c59e0958161988fcae1a25a5c73bce45b627b0ba4711a7d5a43244d14cd7339732b2bb46e451987d7ef73c94c48764f7fc274779d7916cbd69a7db3602545509e937697fceadcc269c9d39c74eefdd157576949918a1d17d235fd9b1a86885381f275c922069a81ea25725c3721b95613164fee08701715690e192f8dabf07827481a457711be255be2026b46d8b2b115fdfb9f3a5c4ed97864be7bf092ecfc6fce641a99ac25a3e804482363ce6860cc2cd0b7f5531d0f890ea8742c18681a544da905497b42a02a703c9712d51788fc6e1e2f664baf80556b02e3de446b29b3c11b2bf1074e94938f38a63b09cb15305f3844a34edfa1aa1d1c0f5824048b338828a80fb373cb5f0225fef5387e2385ae0fa4a24a18903d664b516e920b1b3f0469e328c9e0c95e97dc6f33f8012497ca92f36ccacd671982c5afeb66cb671cc82d59fbdeaa923f46ac58e5cd0b8a1521b72a6c83c338b9530bff716b15a3c9747f3bdbbf9a02a9718ef00fbf0918ac69d5a6359d580ba638fc850707d618f744bb7d6f8652d1647f916b9a1b7bb49fbe05cde5ff28a0d08937045b03e0c94e141c76a08ad77f01cdfe1ad6e50cbd107fbf9b1f25a14cdcf54b6164d8893166d676c9ca691a04f95f92bd5217b684c6821d66ac8efa4c22f666be673899cd996c2813182075c4b3521831095e2ddf195d83441993f5c282ab0c198b0422b8837b5fa3dd6731d06499504c5fe0ff514540dc5136780f8441b9414996c35f19052cd5c88c0c6c955dc13438aae772a0afd6ac8d0056625d2dacc379882b394885e4a6a0fd646758f2eeeef271f29030948e50134ef4b6996345aa8503624fa9b1de3f470519f7de2baa545126272b831275b3b12f3fe8afdc5a8a36b425723a3070d2dcdc70fad115e44c320a395db532a5ef2e76560f176bcfcba7fea2a8d0a712fc8243dd0d4609dabf48b3ed5696a3c7ac45e394954eeedf593a19ccf2ba5086b74417f526d12c89527aac737a2e38778725e78008bdd6312980769c44e0f5d44726912b625d5ecb65de0efed1e3076db952c79c9bddb48b0d3a2436c5f090bbd73e8e7e9d9dc5093ad1bf702ddcf8e8f44de03653d31cfe5bf28ca1b6701953f47417b03b613a633efb6eb79405b749c2784e44acd7d0dedd58e1b2c9065d5661233a09737ed60a5f886d36351dd794bd5467efb63b8f42a3dedd1977b753b808320b60a4142e81245d642d0b82f134adedb84d3c87c475d180290cb87182dbd635317d0b4a0ece533a96cd4afc477ac8cd9eda95ed5695d81b265fcf33be5cbe80335870477d59d047cec530e12ad578ec85fccf92a96a900d54be03bfb62c69a605a6edb7036328a9b165b1958cc608ccf765158d2a1ddc2bb46c3b020c708317b8cd57a82663f7b4a86eba5bd91db1e671654ea3843b3b9615bf2e9d1c33ad087dd6b41b62098cbf763118587bc4c4d4b89b4dd9dce23cbb48f17f1c8454464c1f4a6ff73f5fc7e193dd780c2d4a01bd50ba9bfee3f38bab739f63258bf567a763ab59f736291d8d399462bae40662a783ef046565cc49cf949352695df165a67ee3a6bd6d8c05d7f1cdc40997e2cf5986b11e596db73789c273f6837b73172b06d3d5841729a3861f850e000ba3c2e1070f80ce8bcd23c8c2ac1dcf9dd42b75922a115d42b8b3bf0eab6367ba8a0cce376abd700fbd54a2051fcb925fcabdd1fd8bd7d933e84217f10354cf43e0407735858a841225e7d46e78dab7c5da1459fcc429962288ccd2162f373c3931421c13f815e1d2978e1483f5aac225996259367b4fc2fabbd6c10eeff22b1bec084a2cf6d350e76eed65498cd20e6e5819fe7bbac205d00426d9ba3131b47df26392e2e7523f8d282915380d754d01160e0140a55200c20bcc25d42bea88c6125af5c4bc3b61df038dbcc1104e35d805c71c4d7412453cb7fd1d45c83eaa9a3c7061a5ed10138b366bbfa1ebff1b8f87180a64b5ac7ff35be130d977e690f6956770380ba895bda67c6b1b8691c131c623708f56bf2c3c4f69e55402f143d192a7581a40c19612d8328d031dae2b9a28353ca14d726268ce2408ac46411fba230cb1d441a486bef5af8a8f35ad98f7994d084a16301c7b601b7184d2f364425c82f00b62426f3860bb575c34244b47b212df976060e7be69c8e2ba9d48aa20098fa1664338ce0830ce29ae5bf7e5c5749ed8c24a2510036988838887402a8ee8081d211d2d078f28a17deb48a6e22e56be7b6596de74eb4af15bdd4434d7313ced5b3556111745c2894b212d7789ea3e372acea62631bd2621f299024e7310798dfd019583b38e1b9dd8c40da2a989ba2a067709a1248c0640a48adb79639537570e7ae531bcf29fce07c9552a14a071729759edf18219abcf2653f7f5dab7f5db80afd47cda354f8cc68165f90c93e342c95ae7379980846734fa68f127d9969c8074980bf4f3611118eddcd539b4b67aa983d5b71944a0ec54c84b53a7d37b41dc42e90f8fef6fbf078686a47f6b7d57116ae01c2c26e354d22c1ae4ed622c974ccc02250f4853e1d9dce6fd15030f394eccd31a0ac28a6600413c0577b895120b0a2ff75bdced0714673d2767da1543cd1c820e788455649d0606a60d95931835be555b27a495d1b9ef83ca3a2c58eee2b66eb9f4bf9b0c7a485ba0d677205c18758c5df8f63d2af4229dd5f6f4bd9f7a067874e98e79c622f5252faf4173f389d101fb6a00e1107e5afa3678ad68106c93d3be8ad945a48e09b2f3afb5714a8b5ca8779daa5eb5621ba68785b13115be7e24cf5337b873d22b880c95537023e05426b9430ef618dc9f44506e2db056d5339fe8b66daeb86dfdaee2154ffe48bbdbdab112fd48096bb2343b87b570e1907d1a07892be32c54b48cc8f90e8c8a0e6d4f7278387be9de8c4f87c8e51ec6fe7652c0c1d546a47c9bd0004ec944bb7acaab43d90c2665f3dc9bcc2fa6244d5e81ec5aa286c893e490fe0f22e774922090cb36cae54d8ba67e72447e51b92b1f79f7f5a65f964212277c242f4de8feb33e78bf9c11cc76a86eb486dac88587d5895c6fc0329b7db1144ee503965e8bbf16579a0ca3e320418fd274feacf37c13600e14c889ddf469a5434e2cbc1438f56c82b90667aab7257ef97e4e27e1c5f62be402a7827e9718ab178edb29db0ad2bc6551dd27c46fbe2e39c3aebd4c6dbe6a5f8efd734fd207dd8b199cc6316b6ebc35382d9941a5343663e841a924e335e98d61e84bc7bff275e67457002a0438687e26fdbe94c8612885357fdba63a8dad05da42d32c05c81f268acdfba5613dd687d6a92debc42247d81d262c9475956441b75a140ad7a833c2d2022a2c01ad2171dfc410fbde277961010cae6bbfa33651e87a653ce518b27b7a2090f13e2fef9f52dca2a4cf60018ce35030592ba36fac61d21a5ed229e76e7460e3b33b5406a2a77aecb39be36a7425990d225040d5180a5be573dc803bcb8f3ee0b3347706508ee53e2a51b1c0bae055524f5737e15170ca54823ec1912d8ea7debc2440018a9da7815dbdacbc74d5c550ab4fe4d0bc1b2b05bde17b6e3817ed11b1fc8f6ceea8209258d3278b32ae63970730be2bd5ff54c44aae38157c6d35432f5364b22999a065f4ad0b50d2a4b1330d8e69e9b4feb5261b87feb04339f0083de27d2412e86e4c6efd0c18cd9bb22011659da62f0afa6ca4d5b606696e88ae56d28e8eee64c2cb320ab3665fbe184dfe8ecc5b20665b3a0213849f62bca5b3fd29e82266fb17ad2648b68df0f38bbc0dfa86b229b742bbb0e339b8b2c85e9f2e3d500275cc92cbf356dde7286d83a4d9c5dea877eeeaf324c9b60d126d538be886483bfcf0629a89069e5dbc9ef61433fbd7b09385bb2abae8d259f1a9c6ea5ea4f6f0030e3463f1e653dd093a44179ce83181af4897f49aa3b8183946f61d989b904c8bb6fad8da78762fad8118b30c6b55385ec9fce44cda97369e8b9faa3bc3785c6ad1d55e466a52197947cf06f1d386b0fb940d92d7f9b7177596a5b0ed47689ff436f1a46216415eab3a2fc4f62a04b24b1f2fb5b56ea21923865e3f1295a6e260cd2374a376624501f19f5711f40ccecbb8a2734f85215b33549ed63f0711e66e3b583a0e1d74ba431f9fcc9e31834516f5cb709255d0bc6fc7fd36b957533c427cf6ccbf2535a23b9125727a3a12f630420bb9112d27f640600c50446c27c4db04c09469da2d2ae2b7ceded61022b605dd9dc31ad25b4a7c15c85b28bdeff08bd9cba6d2fe344a5964b5d3ac5686e5bf1d90d3920af5ab108b0370710f2962bb1bce9dd62fcb3c7dc8ea4443b862cea473863c5825b099758deb9bba9e118fe60f3933a1cf3939e4bab48dcc07bfe0c5a392e4a404e2a271cc99fd9074dde6f9c606000c77ef13ed172f1d031e6440f90b7d073382b74926f472789baa0882fc897cb21bcf34aee790af849c330e809d1af4fce39a833b1373dca3197f16bf476492ea6a961647aeccf9f0187f257a9aa33630913d7336faa694731794fb37c0dd1544f9d8579da5acc7a3716af02c385ba6a28b20a5b43f36963a60c2309439f4e83aa5ecb6f412916001ab2a68793c5d6eab11433f6381bbbaa352ae08d6da7c6724fd7744a3b8cb3ed68078ae2382a33536b9e14ef7209dad0aff08caf6092e1efaeee31be7750056063a5e4d5bf5e7f4226c5e90a674c71804060c84f24e3923906cde654c63a21c383ddd39a6503e8326318ebc55b38006689f6c6621b611f49d425c5094cc721f88a324ee3a4ee14342ee8f6dcdcb930a8896632236f6c6b95621d3c528141f06a790b25e330421e285c0172c2a67d0dccf113a355225283ca85251bab4e5f78684ff43b4e2fb3ae0616ef364eec22134a444b7e2d53319846208a41b9f574297415dcccc67ec075f1f4c49df7f609aead9a9d8767a2ac776409399985248d5c854f24462623d003a1f3c000561d4aca1557a65cce5e0ce17c9f6a8ea26ab62b06c9743e3ccb8ba5e1d62145d94a443328c325d00659652cf1feb1ce086cfa549947ebfab878824a1d4a5e3abe4afa19024d5afd843e1a73ffe58fb388141604700af3f2a6cae91f5cc776299c1a0e7cae1be1afff1092ddac28c09c6599dc13f3408e454df04f9fb50b92502ac2c54cb9530018f55d63ad94352e906ce145b7b56643f8cbe4bcda592a37adda822d3460b1f1b8cc738fbc961c081ead8202919da924845a154c9b0990ca8e0ae43c42d0452e881143703d3cd7e38acb3cc70d5948ebb77e5138f2999871245355b060c9e211a692662ae3a89227b649f1605adf55b8ddbb79a9298d614cf8e143e0bac2a1717edeb82599c95883f149d0ae27373239a1c25c745910a8d24fdcc3bca5b0c8ab23c0e221cdbfda9060d46ff2a3afdf609e9e8bddc1c9d66c1c500549feb55b9992c003b5950a877abde6e5d18df8576feb91ac2044c1f4fe9fa3a111a080de47c600a999087fb5024f1b82617a0cd151c73d61b0c23032a396a735c30ceba4a070919b68fc7ab96b224a8927ae0279081f501f936ed442239b39481d7112b766c57db85d1bece184c4d38cbc5c3308c55f4f18b92b12f9ce60a289ad60ad29661bb78f6b5b315f53041358f2e3fd972ef2db79452a62403c4065e074807ab2c80dcd5dbaef0a6234ffc2deb642ab988e71d4ddb5636a9b35a6dda73baac79c2fdde3b31c61877529023052795ee8da9e8c2ce7dc3e5e54e4e0a6e90059095e4ae7a0cdbafe6555692b5ae59497783e97c73afd341b756739ffea1fc5aa7741f1aaba4f33390e04ece4b197a96eeeeeeeeeece01f7a92b3f74ff8e3fdd6daa74db062b17e86078b333b3d9f0e66ed93f76770cb8aeb3d697f365ed6e4dd3b459e57cb9459eca9960e3f5d830a0b96a6a66b46a0d0da6332d1a7466e5d1b833d3a0e9b99a62d6fa926a9aa6515a5fcadafd513989c0fd91a75eaf479d62d6fa52d221dae5a455b31bd75dcec58ab1d11ce346ff0d44b0d6f549a0878d8c1977ba0fb7ba4096dfda16792867828dd763d6d4cc68d51a1afcff73a66bd1a0332bcffb80c6995b34685c356e6e8e06aad1f1779aaea637a72b82f446046eadb5763738d7ba0fa533be0efafd318610b8e9f57a50ec1947494a3b00dcc8d55401adef4889b17218246372a2718c4fa7ac4c71f20072988343767f8b237b141e329d654aa3709169143032f5c01299823e04c873424181124fe0f05fba0770bb90f79cfad93eccafe53afded8209e6de000402e40d5c6409854b962fbf5b220bbf36e09c1d2859fefc77504aa71f2c3f04f77121441ec77f5a8a6e3a92ebcc191de94a3a2475fcce23269167be5371a4a8e3e6f99e142f18ec9ee4543e80438fe5d94ecc392754e459f980c3fe98e71ceae993499edffd39928343f2c7a9b816bfc28900871d73a4f95286616d6bd7566b6dbb5acbb5ef51d6b666adb5d63b8bb5f6e50ff94ff420f7e1de3e90fb746fe9dbcfe13edbdb6aabadb6daef16a290ed56151c25dbef19b2d5ac6635ab75aef9e8dcd9dee467bb6f729cfd42eeb50f5b1de7b2b99b5736e7d772f66ddd6c7356fbcded4b6bdfdab79bb5cf5910b897cfc9e75e0967adb5d1e37eb32fe49cfdcd3e5784638ee58cbad9ee1ea7fd2e7adbf64d2b58be07fd41e927aaf6b51bfbf5678e3796e5e07c9cea6a79c1969382fd1f8c0edfa955d6121cb2b2f534ad4caeb5764894c33e0a9528c07e6167123a2545b9b6d47eba6eeacf9fd95d37b97e1ee0b9f282713829564891b50f65ae1ed53dda4339d84f0e6aed147934ea6a2d4aae5e721ded3d2a2a6bb45ba5004b1dedadc558622d8b83dad7dee2a0966348c87d9a7bed8362d6beb1441df335a356c770159556b5aad5aead949db1b55f6deee4ed31dd4767d97d58ea40f50ef95dabd90f5dfbce5bba5c6dae98edf67596c8a3bdbb5a8b839a92eb68affded1d1d82d8a3bd2682c8a3bdcd140c827d7f215792fd0b3bdf7c60434d9369df4cda7716ed5b49ca082bcbb259355160a1a8e366f93cb6aff4b9af1fa34ff4d97ad0ca7d00879652ecc45c82c32813923f1dc771dc47f791dbef6cae57fdcd557fde199fe53edce6e2724729a5d43ee78a6536177dfb4272db218a65320172fd5eb4031dccf183478ee992552790a84d9587cd85b093c003c26d28aadd17da149d4d126e1be6981101ee0f3bc83187cd148ee1ce40e4896f9302acc4bbabe477b3de0c4020fe582a5e118b1cb5c8f185441d5a5ce5f01f8b7770fc0788d4f1ba7d54417c2fee18ea1eef6ed421895cc7dfe219da3c3351181ec7dd0de449f3fcb8a853bf46cd0622b8bc46b0be94409e5f9e2514f180680aa671f38668948a50398c53d9ab046895b4234f5592670ff9afd7ebe5ca74022eed8b1b92bf752618d450a3788e8498d51637fc6d123ecc2260a0c3f41f4630448d6b71b2458401454b60050a52aca04a0a40c0000695279a24119b6222891c0245a7ec112a8ac91ec6a9305231e51876d84e9e38a143f6ff28b2bf1015d9674fc63c49226f39ce9ec86e105dccd9226cdb4628410a4874e2361712ee3bceb5e41f669bfb8ab0cc5fe22a324a004cfb250db3dfae9bf937f3bb01e2ef80b86ddc5784e31dc85ff2b0af68fb8c1ed6bfe461479634ccbfc82801305fd230bb7d2a242004c316911c3d2045fe29e0d9932cf9fb80a48efc1179bce0cf3804cad2bb01e2df8008f3afdfa861fe0fc8a0bf23454b1200f3376a58fff6a1d0b006a27d471aa6bd8569d6f55a107c333f20fe40fa631a98190fb2590f5b7a78aa8fa38e064458510af2b94f41fe9548fa23cc5ddc5f570a4a60f73bbf48ee7d4e3a40092c85f9db1779bf7a7715d9e7bee3fcbbb9c00d03f0fb8b50f3f26f1830f3fe4166de679e7e41baeea377c300fc332f029ef990e09a2f76fdfdbd6caebfb32eee9bebeefe20dc37d75f7dee4322f46aeeed872fce09880c3ff7341f12fcdc8ae6ef77e43ecdb77aeb3a8285e62d37f3dc8b809fe66f18609ffb20f639ee69be23f2b999b72fc28cfd90942075ead3bc08f823c2dd0fa354ac6391ca3e92d5dfa7f990ccbcf55c47bcbfa19f04a6047603397e52e7f4c375fc3d1d7068b3fbd0d2e43af10947b5c4fc1890cccb879632ae137308341b41961cdaf8835476106452eeb3d1802053ea3a295d1e74064116e54fb21c33084340b94fe4c24352064116e53e37120459948339ee42f837a7ec92235bce2ed2e95a21021ba2d312ee93ba4cce59447a01679572528fb97b019a52ce49f4cab48b9c942a21ab94524aa7b3d2f7a792259528d31523947a1e15b14297b13814c150863ad167e30515b6107db3693bcd2e6470a29daaec9629315475d99931854c0f3bee31079252ca3984ec986c629adac658f1214e7529a2d9582c56449b2017a61514a52d98b8b4e82b860e5f76784d22904693155a88402e44f78b0e5f7620bab43dcff9c18cc562453a09a2b865460523c2c42d5d9a7ef0b158ac0865aa744e2f329b580578d274058b0bf5443033cb7da683b18894f983dc4db3ca49820ded6fdfcd2b5df3356b6d54c1f6bbf9409b4fbfcaf9b92dd0310895d03ae76bef9a796ed57e24cc29b374052072103588a07f4ffcd8e39fc14ff6982dd56cdc806fe607420659fbfa91d0ff397e379fb50f7f7ef9a97c12eae723461d717ebe7da9525629e5cf69e3865a6595b2ca2aabac52ba56a593b65f7b12ecd7b73f6ddcb06df65d73513965a63382ae4beb3bae389e5d9af4b90a93c0206b4dae093c6edfb66ddbb66d5dd7755d30a9b1b4755660212cd3131e5a43684f74d9df1251a3b8f707fd5ffeb4099b60360126fb4f8b8b682fabec6fb1116e2dc6add63f285faee9e3fe16fbb7fc1f7cf52b775dd771dffd9eda7eda57bf907ef3935ff86202839aec3ebb6e05f1bd20042f00820530a05015ad08a28aa726289a7050c2bb3800ca181f4a308529062a60de0598245a886169053ce8d084c48c2f3fe8f49123f2cc9f56bab421d174893377e69c139c75e668b445b5eb60578be9b4dad7aaafd139298d21bfd7aa24cb7da8cf7ccaa2f4b183acfef0f67b4f16bdb82f403385617ead554a293fbf33f7bc9d3b197544fa425e5d5bee3a76ecb9a2582afe909c5ce3cbb7d1752509f1e78c2fb812e5e439e5b51f6a4f6db6395796b539de7c104e294467adfb48c9e349fa02b3638f26b3a6bd5cc0ec086a1afd802e60e66982e729e35c5940935d7b22706fbf24370fc8200630ee1b667461dc1b4d5891fd2516d67100f5b72013c67d4b509881d97758126abf180750650b86e4fa937e37da195f26514e8e3664b083f14a07e79c133b1451cab914e10517d3071a5cbaecd059a3226cd0a2c834a91c23b23f36ba52c571ebaf3cb14d96072a29507041ac052750725486ca87264f2031c505e64a1c1d91030d3840788470e5056e872e76987ac1e6ced0d6653dd682592edb414aeac45cabe6025dc72f0b27270c0763072a47ece0c2121dca408137c871a6039819e620c7990e3dc0408719e81238478b4a03bed1e453b8468e3310f820bf004114a8304090343553c036729c2529216bc09fe32c49072f4932cc179252a05e585141182b4cd413704e8e332b58aa073000729c5939923fe4a045e500b6c9719683146074994d304d8eb31c966ec038729ce510834b8175729c55c1a24ec137729c55d9d242952b4daa205126aac420af601a5f483118270a9a036e1df1c3154903a691e38c8a0c14c98b1d700639ce3e0025b1c03839ce3eb0f45445daf00117e6177c93e36c8a0be60ccbc87136258a15a63079165394a664a991e3ac8520b630bae83e5fe3ccdec96debe4b66d92d2aeeb26a5b4eb3a89e3825e0f19001c3b527ad327ba5753dae302903c33001447e540dbc0ded872380d3ad7b5e101601523cecc0dae4143a3a66533832523460c1994b2a24b4a9b560d0d9a1af86606670500cfc6cd60fbeeb12eedab1ad09c79837a7bf7c80d6e7485364bab59a923a5e44046c771a33727a594521daf477c2925f699e9702680560300d4e04ca0615b3338136458ce8418761e61b90d951e79e6441263cf8c42836756de3c727d1ee99c3381b31e7be691cdad56a9c769c29449b27f7f93280a142aa5f46ea0924a293d1be4f49e784ebc1a3c1abc19bc269e0c5e0c1e0cde91f782e782d782c7c4eb80c782b78267e4a9e02df152f08a3c14e6ecee5abbab0c80053aa035a28105400d1aad193262781296dbd4ece8ee9edd23bd071a3cb3f23cc9f5241d373dc9e656abd4ca9fe8ed2ebfe89dc4e300eef762787230e906b4dea85a8e766f0cecf66d9c44e2925cf7ddcde07a36bc7b61b09ac1d9f29cc13799de3070ff2d83fb2f18b8ffbe00f7bd5fe0fe5b85fbaf1733eeed02f7c7cb05eebf6470ff7501ee8f770bdc7fb5c0fd97cac6bd5964706f0b70ff6501eebf6370ff5d01eebf2ac8b9170bdc7fa736b8f70accc18d54e77aefe8d82391e8dc9b5dfb8f859d3513a5c381e3460e0e1c1a7caecf00870d1c3870e0e0b601705bb08063e438c3210cdcfd409f58994be0e044c280030b73061c4e984798abaa38604b55023c8504860a8104a639ce909890322011518467e438434a9a304042aa39609ce30ce908c96826458a0b3903aec971c6020f9509be53f04c8e3316649029b0c0820730ab0b9ac51445c10a5149e0558eb315bc10816b8eb31598c660eb430a94478b4790670e5b4e27e8dae58d479ef80143b98bdc33396ad25a951713dd3b752f51094453435359361a3aa53149534ab22d4d4e50515262a86c4dbaa22542394129292d29292929c59496b6404931313531313131313112122b3a6a293f4208e27d17d2f03e3aa790662284c8084425183922a3a79d730e2132244209b16eb3f8d1fa9c9d175090100f20780a7b5916e8f879e4c1799c56ab256539000454375bb7ad76dc666be4be0ba45520211e2100113284c80843255409824020182414040494c3bb0f5319a52a26a7a728a9a9315464644a4b5b40076d1093c52c9c07775e398084989a989898982ebed6eed0759de4242972dfd44efd64eb6c2c628e682fabf539e0ce0ba8ca2b499dd44979da49656d906ab55a3f5a9fb3f3020a12e201044f612fcbdba7bf677bf7d856eb73765e4041423c7e00b1b765439b711ea7d56a497d59caf67bc8fa2138c47dc256ddb6fa423a8044b5d98a93436defc0d1d2b4346b3f967510d45cdafb0587b88fa6fdf40a59baf70ffae1deff4708408268c95ea55bb9ef4a0ec66ce5bedf1df90a6feeb809526960c4fc835af22728a8837277412dbf321e3fa8bac8f1a76815f3a245ee1f42c447c65355ee2fe213621c44ee67e1ccbe18e5f0100a0a5232031409a4e0a274822ba06e5001010b628008420b2a32338e6932a6e8a8c943883afddd64a37b7c4a9fd36fcc654e617291e7d4c528cb9953938cd9091e0bcdfd3b4ce430878dde11737f10e9616c5a09018569ca03513138d18089155c5182d8104fb83872a1c5c2b12c203c420063858593d3a52a7390e30c4c2cdf1cee487dd179b7c76ece3769277d774dbed35aa936addc9c6bd9ada4bd01cc2992e9d4344d9bf385a4943fd6e22f5dcc9140606e1aac9021444628a152b99343cb1e6a31a49123c5da07d4ca2637b9054d2aebd30a7a90d526100e332a6272d644519e5fe78d1682a6f978d3108bf6836432b23f8f2cd91f489d53647bdd294f1a12ead7cf5ae1224fbf90737f7fb4afaf7d914b3521cbd655b462a2854c59b38aa6af5ae0e8bd1755b5cedfc92da59431be9c31091ca7972dac6c367151923350583649318ac151132325aa98c4981c19cd781175c8cf22872d264746f26b1522cf97580b4a2c593b5b59c8c1feda754489932c3f2a69926592dc57f2a4d42604987e8ff384c3d664218b249c62d7c1a82dfe33c475bc25755c4c54d49387ad266b316eb5fe41b0152b7250483d412b044940f901fbc432460bf6a42511608f3df127969ed0de67311965ff966de1f88063ec891fdaf6a456d8c279e538657f10dc89524a29a58cac23707f158b1c3b6506c54340068929cf821b0987c8e3e4a80aae6a5595c1c148c4b23f0b5f6ca40af7511755f55155171519d9db42376f821c0195d0affd2f7582cc96c4406c0d14b83f877227eae74ed29922ccabe0b0a598fae357f5115bf20bb85d5236be7c014797eaafa562809be2a44560eb1e7194fdf12bc60ee60c2f9f220477825e5ba0b27f8e1927387c65d8018a64d93fa87774f62fc199aec09e02a71e6259092c7ce538b845cbc3365be0f0f5faf91c29a9e433e63ee194e233368bdcb730994850d80a1cba511519aa1a28f03c8201368d6212aa8abf3562408c1369886e1ef9cc1438b4d966097bcc417b345f7062c404cad154eedc00096b28d614731e1d315fccdc79a5ec9f13653740e6c79f23acbe60a2a1f78f1d4367e304be3d614e6b664451f6c71e80b97b0b87a28508a7a245a961715604126e264652e029fb1204e298604cf6ff1b2bd44a1d1c9b231ce24831418c060c1b848d0b60cce29503941d65ff9dfad1c52a429f85a705ba1559364fe09025c38f560b636b4130043390fb7444e2d762dd8ba9bc74f4b17f0c38b4b6a78980eaf6a40b3be8e1cb18fd7fca29c28983898092fd59dd81ff0c0af0cd77d3df5c2d11368143dc780ea125fbdb8e6dd1352cc6188b10a26982f12c7389ec3f937cc97f1a16674238d13ee684d329fbef48f9caf1efe0171285f463bf6b7150e90987f82281c399e42b441d7f18b23c51329a31c2a1bdd3a94b0272007594777f160c0e6f16e27a80b5b0b230c29bd35a4acafe38aff60e6438bc2f1564135fc27f9a285d1a24ece37247ab6b0e8bc82c9fe87a0f38f4a37b248f9c34f1800a8b8482ae080b9d524a29bd037f9612b83fd248e9d172b0b3c3e4d7df7f385eb006067f16923faff7257a1644118d141cbef24bfee0e4b86c8e5e8b260a16e2815974099912343dac3ea5941af123de81ff350287f3ca7038633ed5cd97645cc7df7ec16157492aca247fe6874d95434925a7424915d22c6495fb845dd5656419ff693212363fc9e42475fca9aa5cd280c39e72508c4b317555958451196b061c82e04e9c338b4b822bd9df5a26b067d954b158cc059894de422897c85e644896fd897cc8a7a6b2db4081c338e543853c6fb04c4d30e69627508577a66494bd084bbb11efc0bf46133652c4c112a6e0f0def480c348e549388c545457ba221c8bc572a434e2925246d9bf84c348267f7f242326b3624580a092fd8dccd95435a5cf2fec98ab5d471c74312de66f53add035ec0681c3fb42f6225f2237cc672318d36170386ffae3cc07aa51fe44a9d39eaba5acd4e5b0973f422d6ed2c4a61b54f1a7486671e14a96fe56eaa5fe9621912426f7379353a5d1c87d3a8ae9587f17753381ea9f4425b811fff1be53e04207998aeb40717d741279bcc0f73b92e40045321816dade0b6dddca2b2bd773b54ceaf4fd8e2881754eddd44c0e3612ce4ebeee6b21e7cae0d84d19221a1aba39fe1075c52ceb8d53d2afe1794093e7570687fd949f86fa09aaa346804b98df9a6d5f59a93f1fd0b0fa1d4902ab2fe44f4352c78983501cec28a8a9fec2eee902fbdd110ea31411391475cc6ab5f822bbe400059a48ef981d5041e317c8116ec0f5fbe3d0080e6a16941283a03b12ae398c52560671d2c4c1232232f25420eaf82cfbea073c44080db8bfc64a0b96d2669d7f4f776c1abbbbbbbbbb9bc6eeefeecf31cab67d44cca8aa0bd7b1b9052a8aa9424a154855e050c594ec1ea62ad3e4380b738515ee40eeed72faec385d4eeab4238de1b563a5abe9371fb46c5d2d7755a3b9df4e96ddea10766beb9aed1969d462ac1aed9c1a4a29a5f4ee9997ee983eb28631c6183597f2676ac0ed1c989a46e9d7d76aadf4eb991a6aa8648c3c43979c12c8c128efdc893cf1fd371a1a3da7fcd0a57cf98c39534ab7599b33b7e4b5f3e7c718e39c71186e3ce072479e286ba69215b69c915b4d71dadda6b7daa6a771968b41848d14eefaab5abba84ab7d53ca176b5a526b09af1bed8f2adec69dbb23dc99e3627a6edc9ca9e3627a6ede96ef75eaf87f677f392929234ee0eb1b4d18d6adc4d5ab257ec124d5ab24a59ec12a5dcd59296ac5216bb44374be9e56c0e542c77b7232a5d938e0a774405c909952fcfbb7127d0cb59ee6e2e30a199c1342eec9a71c9ad76b14c7737ee84da75b6a397dbb8db55b9b5a00552562245c490e18814a19a22d3792b5a25adad81a65f37adf17a705fb3d940814e3f86c81f436c6c8a8aba1d7742b5b5d2cb6dff8a5dbff6d7fae291e376dc09d5d66ae9e536ee76d3c620627eda4b5ab92cb9dbd5fafe3b1647c6f56ec79d506dad76c69370313c270054ed5c4e5a35bb719cd7433ef7b1090a8a27ee68aa1e1fef05763aee4759ddb47ee268d7d984c13dade7ba50ee832348c58aa2d77a9e84923f98eaa75a521358791fb8ee47e76bb227bbc53ec99eac13937dd2644fd689c93e7576defbd77a49494975eb8658b2739b75eb9296b42bdad24c5ad294b2684b736e5d4d5ad294b2684bd36ad3f3bc551ff5511fe540c56e9d3da2c23591912515242754fa880a9213aea9782b8db3abd54a9b3122757cebde7ebb90bd99e4f4464383d2d7ac112e96b14698704631ae99782b8db33333335d4c2298d5f2e28e22dde3d5efb52c4c45ebb72452440c19aebb448a504d91f15616d3d48e20fdaa812dd7af238feba8a9a9b18192e2c710f963485354d48f2150d129cac6c64606f4e2d13dded473fdf8e291e3c523ca805897e5f5d89ee5f9f06c3f06e802c8fe61b77df75bdcb1d33db2a30edbb5b7002ecbaf7e7379ebb896fbd04450c389e1aaae59e56019cf832643e6b676341afa3783a537e5bc1ef3bb18dc7502147511060a25c230318908c304441821660d61b28449a2228c2bb6a035e0ee8a69027ca19059e0ad89aa0577b3238830a202088a02d6729c5191858aa41e7af0122687511605db2c21a1609ba59a84ad161278d9018c1724315e629049f8e638f3b2e4892e611481a90fa9aa10847e448b9dc24885a0b3b5add6e7d3674e29638d49c498bfd5b2d695a5f41dee0deb1effb27477779f19e2ed04dfba53ad35d1a99d28f79a457d257767c93d8b6644233b69c8bcc9f334c8214ef69cc046138a1744317de00433292752a8207b9e378594ec4dc1c58fc0878e95bb2e0a2cb96bca5d17c517b99382cbd3cc302506b3514de4cd277299b32826419695eb53af479db10bd401729893a91358f464fa849327520085109982531693806292e5962e55504e4e1c65e96eb3d7370c9758a8742e76504045762370e78261ca4c64143cc9eeef43177991e53703a28e1772953d07fce14bbeb5b6150b5d0ace893c91db41e1ab91e5c7aec992522ae47216d5a7b37d6776cff6b993e305a47d562cb5b2a9a3b17f479e6f80b8c305a4e4038c2c47ec220ebc63eec842aed19bfa31cf1998a44ce953fa207d4cbf45a7bc821d941d08be767258f003fa316304fee467457b6923105dcce9420e76dcef90c6cad301f457fb99cf005167fe9c2b6f8640609e614f9e17981f714ed4c1e50894ad75af076d49ad90e8f9e0aecb1fa8bfb316e31877dc883dfdda7b4ee4e9ed3b57e8597bca85318a3cb5b73f7337831c9cdfae1bed03125ae1808986b8167047aa0a4527e54d3de20e64c6aad822cb1f42c47f8a666433313164f94344617096553865f946dc2756db83fa852c5f3691e5479a20b0ffac0aa91e77ce493f2bda4beb47b28b10e7f8612b7f0ec1d517678cd6b72543535350a09c0c1d354122d28390212378cfdd1ddeb76a1d7fbf5c4d0eaf2cbcfde5f03addeeb9435350029ee6a8748cda8972e9d090000040100083160000180c0887c3229124c8f24c50f50114800d7596406e56309407832448411404410cc418428801841863087146a12ab3005054476447bdea7972d5fd68661121a81f00a19168143d9bd0f94107ed25a1d7438bc2587ecf8e34e829fe8718a392725b888a2966d1efba0002461c8fcf04df5d84d98ec753a1001873ac71a77bc7e49f895af60281db5f16a70ca0d342d8ccc14b1f57e986dc0443e7033ef4a068193d8246ca987ad5a80144cac854a5743056f5d9e43195928c33249b00b610d6082afa076286ce55514616d614651cc7857fefc5dd468cebc66e97b4e6d5598fa0c4a45fa78a6bd97cf4af3f105e8267f2f7958dd49be046a5aff432ebc0b1e8e50e6fbcd4bfe5d3b141a822c385d3f94cde323d371d2ca696d19b800876fdd903c3d75583646d9ffe4797e3f68bdb53692fedf6ab44831e4060f37f35b3bf31007006617d6429590e8ea1eb61ce7756adf7b820c2a1cb8ddef85286aa206f554310f57cc190743b4ba319c48e58195f3fe75d2a368367949dd955bc457e57c93e6bb40dc0cee03e36415b9ee83426a21d4c70e123fdb0155ae88a1be384acd19b6144c32b10ddd230c484574838266468af3e6748dafd4d8a41aeaa7e158e33744e21f5e9d9629497b1202ada9ffbe7c2eefb8b1873cf736a7e6c98aff3b472c706077e99bdc33e8f79e9d1aac1c8eec7fc6329e9f734d111fbe6d73ac8fb653dad70edfe79c0666adbd69f78ac0bdbfce1fc012ab0d33624d065b63f02b18bf94155ddc5e72e4925595f10a2bd5967f73835fd4e8ae209b44dc9961817eb0032a61264a43a4ac97794b47f296495a236ec5a97044f806e795a2125079f95b505ee73107416e23780fa567f715908e6bef564905dd13d1e2790335f797b7b5587e79f6c1ef76c3d71fdc609741ed77e1199d1e8e51c12ec384f9c88fbf54e4d618357b93e7323d8861e9b15db732b89e28489699d2e6170e803319f3ff2a7a83a0e21b2f7ecb23c10872ac195534addf0291b4e0d210fe1916a56666bbb8ac809854b814f185d8360e4b7f623093690c2dc46bf093641f4c40e49bb6919c62dc90208fc3227bb4060ec23e08c5d4bcd4872d982ead69aa23b8fdce9609b4d7f8f2abea03215ae8ce76ec7034dd8df94e4047f3f5bb664509cff3698a112e85c16b725e2ede78483792c87126bce09b47c153a658bed78b681498f014d7a7503ae75e835182074bd5b88ef078aeb81b7fc0b30276f3d34723cb5b1ab12bd4944c91d37ee8788007290feb423537b55ac0550486985f7780e8a853e2b97ed495f320df018ddd99d7c969e04f2d187fd4f8e19947b9259195ba002f4cc760263b40a0fa20939ee7998e00a99294ea9ae1a01a571f917018b257ad06913b11b2e00d14bb30e4d43ba5a666451a75dffb0da9633b55a5db513b1c730efd499c9210dde7b160a0cf5471904406d78ca25090e265e3fcd7b7ab58fa9c78f71df7b7420e71a8a882e8a666b8f08619453b9073420a94aa53cad40ec3fafeadd95d06b3134e92cecbc6d3a9b96a2c57f6cdabec33d4354aa5a2fc40ad3593273e412d0253a4f5896ad9a2d0f2c8900e5b3d6992b7665a1425d1c7c2ebeb300d4899081b461bce5b2fb58f7a6850c6738009fcf2718dcb1122517edad30e5ef2ab52a4a7b40e64f9b5c1cf990142fb2d868a088ced7d90c2d7b16c8c91256d2169e6ba236f2dfcba0d2649604908fccae0d8df9b910b4885be82ae023e3cbe6de306a2e1b5ed37f083e98249c6501d734a93fefee13a54833294ad5a66163d286a6c15c6f407be06735c2aa8e34c62cd961ab50e6c0c0c01a1c3d68252edf38bd6463c86c01dd92160efc01e165ccff6302bf50c55e5065874c9ad7bcc81c2e5008c5a936018977c57d0e7ab90d19020a175c345cb07d0a378e4156fb751357d23fc2f9122f6ebc68a1813467d2ccb044b90f30e33a3b02169c45293deb56406e93a066ff6056bc9d4917d008a14203d83ebc15bc1992ffa4a833b6f92b0f5bccc44a9a5ba70b027be1b16a3e9304d672cfdb462836c23105c0f071fbf7ebcf0fa940e1c916b9f29882645fafcca87e9f4b841430507e182869e0ec477d5e1f7a6c1a36653402a17589409f54f89228c837025ae85c706dbf063bffcc96b4b622adf4e65868cd819dd04afa074551ad4d280464da098dd891b20e307988a07e2d3b664886a1e0c716e521dce06825f56f437dd6c5a14a269b5b9d0bdcc4a166d4139a2bcd0dd169303e8b9c37ac3bb6b3aee8bc5d7b4ba93c9a17358a2c8621bea7c12ad42b890547041ef63b7afa8293f813c91e142c55d483849eef29a2a5ec220dd9736b319e14dade6d4086870bbbd12ec9f433fef14b34bcce5db96e8cb59f9bd7957d3d82b2c8304c45badd95f21dba00dd5f0168965d03571dddeccaf7bf0c36e4259ca58d9a3f020f916f0ee2669abcd5393a635d696bc44d4927721ce7b06f7ea4dbd9c4593410f47f2d9fee811c0520244f3b2e83818b7c76cdb071ad7bda9a46f1db681d25ac1e445314885eb741f145d5787a7d051d6c4b7ba882f6fe705d16c1b7765204257310a8a2ed7c46380b012928e13426308db214045c9771fc820b8b431d7d895a99e06f7a2ae12e220963a197a1aa5e5193b065be126564adb99a704cbca4905db63f712d6021ec44d483fca589812e4c2de6d89bea9410d63da6571c9fd7a853d8bca3b6e824ef47694088ce288b4eddac835cf02d0f895168b95d0c9cd2ed80a7133a6cbaa49f3c40d0529440a136fa81690c213acf7506fc00f8ccf53f08dead408f079b8072b5c60aad68528ef9089d11b34fd9aa5625e58e2cc66a1bbeb72681568fba32369060a0606db2cdce110d649889d69353052be15b25d2af333e9d82b9f9411f7150ce374aa6e115921ad6643c4840aca5cb9a60cda4efca95569a79d4162ea0d4d7ca0414c93dd4a7feb0364e58587f2874ffda3fc5ffea2a0a4a1da2c7d541408c6687f0608dcee79609ad60169adb06ea48da46dac72b92099223addb8e64c15e7d3f0bb1e66e523b392a6759a31c7102b259c3b4c813c530125115582d6b79c7e8434b4e18b0a7ff595a804e569958ad7dfc30e8322a51c56cd0e015dca2b10b54661a7d22fd1ba024c98a5af67a5107f42f762c135912b25d00a7a6469d48970af0f7245399e000fdf839d186c0a056c07c4da4f410613c25a383816fece56bcc455f52d850d7378f080d001161b2dccb38240ff0a2cea5433a51f3f5c2e84b8b865a9cc03921b18167139528fb4c256a44c884fc966a5091513f58581db921b96403bffeb96c0e36e0e744fd1f3e692f01c7529eda19f9bf120a9f7bb6d7e4dc662aa6480136d22763e8f2958893cdf86398d7c83a8533e58192dcd0b96f7183d575cd6c90bc463e460fd1095f24cc7a962414d464fe2607abdcb7917948966a151619b538c1c65dbeb2a8311dfd8481deac47d87def0650d199ff9cd3e648d54b531ef1b00855b66526ef4d393cc50e290a94baee530547f4186f39fd06f94861ac9927f0bcae771a5893674e04f48abfed96716b9ac26bf3ac90b6ba884ce00329d78f2f66d24b5fad1b32dbf70ca2c1836dd11e55a0bc1f2e97a1b8ad4d2a45618a0454b43c1f08676200459da121a1e990a548c0bb0dc717dc583a8aa17105aa5c4f739d8b122093b02971c94dbfff79adca2dc5cc94e137f4e7920db34fcb1268f2a3f1b83e86416d6eab0c97256b3dc419301502c958ebbbfab2616cad92f9c4717e8cad8928d5f644131c97aae926e46423afe04bc257d3a34167d6b84486593a530bf1a18d0c784281ea63e2a22bdf8192273a23bc13d0ad29ec3fa979524337faf3d6091aa9d2f77bbb89bd098395741d12606e686b6c80c26e543cf82b573e7bda89cf287f40ea901818bb35f4b26db402340f4ebc8c5644a1d33111a3b593bd99f1f1e6ea0b646e317811eb39c7c89fba0aa75b11bac549928d56f5d9bfcf244dcf3687961a690c1991af1ea4cb59c2d2a3e243071a1274603fcce723fabf6c6cd19b1317c18a2536b9c4871e5aa569df2e787accf9e32d1a575b27e8e20d4c491fdec0939cc6d03fddd3ce3c71a1f37fc2b85dcebd47096882a2db0054c4fb491767a41e5ad66f224f9a41a3caa1b92cdc8f4ec39ba90eb329b8ab4e27e59c0c50f7ee8c8c49734cd103f8715aace05ac12582bfe907e458e840664a01c5ada2748cbe573284217bfab9d2824844d918d16c5600c47f1525e60a40464f5572393513b0f565f65667c2fece0c9fa2f58ec5a924caa1fb0fab12e7b66715cae4c136db13b879bd81d2f1bf3bc8e68c40029eee5da485ed8100abf674588f50e75bb3dcfc8fef7bb5bcab94771ed75ba82eba7db0f8f6ae5ef3c8ebc506be8b061af2b96340497670178c80c28acb4a9e43dd6fe03275979508db4a1343c857beab781dbd509fad8a201c08bd41d4468316a82000c3cf92cab103a4f72d6bd8ee26a5251e3b78ea3a5a7d270e3fd9eb36cccebb2d44718fb5a41460dea2b85860e8b965a8185f12ab5b89d6f090a2673365918c17806b28aa11cd532666ca0abe0835ed1beb076945735e16d65ef5e175c7af851c2dcaf89923203feff226b4e72db0119331d27c8570c8220b6e81c45cc1c1c2cdc1ade5be0a7dbe0f457fdaf53965ed3fbf9ccdf94f5f1509918affffdaee84bf2b7a28b07439a4bd12d679f8d7ae148e22a3499615a994aae977e4957e1fc8e9e19bad9a928d1bad243ab05c9fd529d8f2575ef37abc1ce99ca55c75aa295c4e76f589f89fef264a2e8310fa369c5bfa7b786d42b59481780c6720a4d526a08c88baf6561fe9ee742249c1922c1eb819985c250192afab6756e3ed915b18be285728dc7854e13bcc59b0d4b5f88f953aa7cd987140386496382020ae3c54ef092289f0e81c3073cc9230dbe8754e7b04400b34ed55446eedf99d4d56a4c22a66c3eaa518e003c4401e46cd7ba9df6a7d7dae03d7981f8d435c1be3f8da7d33334ba7ddb63cb1e88f1b5f6944b3c94381d5901c8eaf4f2227e41c5f09a6fa5a4ab9f64c0181f5b96397e6212511da8fca72d2f9230586e37dee006bafd07e5c20c4e3c4fb1cb41fee0ae523d07ec4c3d4a8acb83c8d8518e844d1c6ac0baace245ad2a09f16565e59ecf58ca87e8b08dfb56c642c24d605214dbaf7b27955165888e34e8d1cce6217e2bb8eb6b666c630c1ead848de54f3f4aa02ea895720b9d0ede108ceb209460ecf917142592d38b0076134422e54446957741b9d39b3ade244e3a4b20c5b4029cb56ceb07599840cd09629bd2e1bb05ff1ccd047f34a71efdf9d377eae3586c9d36470365d7016ba19205d481c25382331c50d3f0574417162743885eb57807ad213906e2173bc2ebefa094654d6d9ba692c867628dc6ae0b25b4b33c8de8e0cfbb9d44e0ead088d5458c3938f4a66354709fb79ef1341f9e7b77bd3e1b1650bf517237a4217c30e9a6b5d931edd0f1f8e002cb676f6acd4d05f5cbd6f3c5c996d98ec7d0ed8b0913df63a3f8b77ac0042219c76d97b49286138dfbbdfea7ef03b1f127497a70bcc12cbd4cedc76d979a3923013ccec54d1b8794e980a5da3c8e4f3d2dc19e78a360d511a8c0bf61f7815179d0fa78a005b0bfffbc02ccb1b660ce9eda9932f6cdf6eb2f1728b956d8b500263fdffc01cc964718dcc73059bf0aa88593318eb3811e909e420b4a8bea0a8a292ea4b54791bf019f217050e6f16c246d8d117ac08f340cdb0bcfce58a2756daa1d843a73747bd399fe6c94792a571e342fa9747ad5a0bf69084bef20053e4849323716ac21fb1ea39355567b39753865b161ba270851fb22fc38f5ff306425b05120d8400bf4d27881f01116757efba66b9c0cf65a212ab19cf2ba22f04c4cac40fa280fb0052835066a8934083d202f41af7cb19d18b082dc4e2026a84330e6957eab6b666256e3dd27a7937ed2740d202a418881426a18e6be023320b87411619bf409af72464441dba23bc58d91e6d2391ea0691a5ae9fe722f3da41c451a41c658e63e9b848d57cb98885c4da6da57383cc5884905235082eb7611147aaf0bb0c566303794f6710d6a9d1b15caf56d9fa4f125e91e783baeaaa708a15f62ab0901d6165f9c7652ca0db802ed0aa924e405b74535f1451c4b7341e308e1234826fad7c076c4f983f247cc47e23b2828711b8750add0535dd0a9a3d33df0667b6793ce9bb9485915e718778d29e7d457c25d6b4bf76a7effbd0e89575f91e9982a3cdb95b71b3f2152ad4be1815d3abd58e3d20bc9e8d66cd8817fea853441493bdf0a46634f9fa4db360f15238143b90c555ffa29762cbdcb50415dcb0360df85f0024755f0e99b429a0ca40ba83a1b3b553c7d18450b757d3c1144460a6ceef698a562770199e41118f9f2958db8d3ecf9a5c2f67933ce3904d7c59b5a5db7d1db141278fb79ffd7687df68e532f3e8324485fc96385e0dbb35978568340ba3e98421a05365702801e9359fe11a7dec399fa88cff6a9df3423b8eb4a28dd7da06f7dc3890d78580aec56b55607343d38d2d2e66d23ed7e29faa231304cbe97fb7e2cf2b2c9e3b31468c5b9dbc9acbeec4d0abfc56e0947a530c2c91355b7d77f7cc16a0243550076b92e0b36b957be9d6f9f52b09975e08ba701b70470d1601ac666a121e7c8fb8c35a53b194d6e7315b48932296698f3227662cb27b3a0925006966c3b834cadb57c2ad200191e97f094db7c872eec2e4a96d5cf83d22b417f54920756435e109ee6cafa6bfd181d243e0b157e307362b5e8bfb471fa9334d92be100af77d25a1b91b08f8f860c2feab9b30d9ee5e02321c4c831b44c361b5418c3ca5b0a64987030472c165fb4389e1518466136de8678b49fa499eb8a8ee2b9818ee2f39e4bb85e8107a35b99d3cf160689ce0f0acf26ad1c54c11d63be474e9ac61328ea7c4abe739c04777f94ae50f97ef6c540ab8bcf3fdf168084df7571d77564d699a5e06b01262f34608dc1527cc4aee45a5be87d26b9d681082543202e289467bfbce2bee8ce667a5ba7ea7ad663a7b56772694035003827c0a218d83c650816b84263f4c50ca3378eb3455b7129fe423ef0b95252053d712fa696afa83c8d69b24d7fdd615060c866212da5db81a5088702f065c804cae12fabb382833b80f9d9c01f6eb9d872cf61ee641a0455e26aca0bb8631ba152558c16e770919909c766847ba01f9083e03d91ac8ad45e9566ca3735031c496c65743b200ee3852dcb28032e34893ea44fdc27e5539cf22bd2b454e70ad58a674c7763510278387a8907545a1849e2301706395e08ff7e3fef53e858088a0f2600241f803cb66991fb6dfa688fa1bd440f4287ea4ed1c1f96834b328eeccfef138dc5a03ac66cd7da22849018cbd9c787c01402f248e39ee5ad8b9cabecfb00e20388de25a98ad5a6a819d0340234f5a047384886b0013143b56fe2d1386e6cd56f0a8646258dd02c7225cf52ac5ec8920f275b3728b364c6c17ed3cc8bf75cf66176f9e3465c3867c9a948c9f81ed9dbc0cb8c314547a765054b72fd7b10077612db70280bba7c0f6238466b051b1bc18f8f31ec7a85e5b61436e885d7651d4f10de59a9845f42827122eb7918ce8327941b3f53bc6ac2e0b128e4f90c4606688120e0c283d30bf3a5e12f3f67b2beeab7c24030604707e589e62085d879d6cbe00585c38cb7ba69a6b6abee20480430f01c900e48a64f91c226ba44fd585208bbb541c6a5b25791e1f648a7ae0100586dd2eadb086ccd297228d315ea93c918e10286c8ac30386ea0f44ed1e282427cf2735c8e83084121b20f19a6070ec26c09ff50d7732dc70192f225c20b2d14ea63c464c720fba09694c84c6717a08f9b0051fc6f6875aa830fd8140fd860d39443f960807d40a76ca3531a6871a7464bd80e60096c9a2c09a592bb086511ab033b9563ce5135ed728b692e818636b6eb08f81e2bc82cb2f5731ac64c981530ad2271b7de35006655712d718b76ffbb4448868e844e990d80fdf93f7d7a0845f7cebec27e210e710000a50428ec219c5de9288fd91f7fd764c16556fae3124d51cd07a0bbebed8f1f0d7b2e7fff5352ffd79c78b9e597a5265c894c381edd040318b5098df1245f10d0909c58b23fa27f820f10ec8f32024020a70f40400a16c58b833706f22512c812cc856a5f752420f9a21e670fad8e85cd0629964483bd8ed21e033cad4b35edbddb1950320cdb0cf05f77192f53a2d669e8078040f116f89c2e1c2628555e7713fe06e6ec4ec842a1d095e024217b91380ce5413895a5d2d7357560d22dad7b8fef0626c17941f0dbe05bafe8bc2f4252f1f2b6a75320f4f0cd0060ff1a4e2c1dd3454ee1a20dfaa2cf97d30a2deacd10a57c632be4d3da0ee86f92941536bab32744ff24043a6be330c434ca5ef5138a5f6f28c5139dea1654f540d70cb7e13787d081a51648162dc3e6401382609808eb787fd50749db141aa49903ff1cbb012f57f340c3ce838a67ba4809e3a2f7a3207952957d28f29caa725abf2186169d8bfee7ba4b3deca56968c9b662a17442d6063a238cf86a48bb04df28a1b33b47f42afc4d66a3de2ab559f7e7946140a8d2c991294deb0f2519f87bc76e24709fc2c9a16b8f37c96e57eff621ef2cb9b0619cd5db74a34aa24331ef222ba8a531f7e6b5fe5b0053c1c61b0c2807d57b08af7521c2abb6f1b8555bb4afa6e29f641f2c23ea5ce97b099f40669accbcb35138a65479b13d3c0eb0a41cd9ffb251b71b51e1c4600ac8ce64891e29b430c7583baa3ed0cda407e4bd4e711eee692ab8a1f048b71d4e8781763e732453a2b7a1d674e712ae137ceebac4f0ca2fca45e616ba8bcf2b8faa33d50fbd22b346bbdc12ee7155302816e10096bda682be4f24ce55d233b6eb0af43e9677a1e902d339c9dc7db0496178b3224701902afb9357262e2c8911bcfb381d9107e25d98ea31d04b0ace629d7a43c40f2641fc3c3ca8dba85fe6def206f21d0217f23dfeb2e5cf35333baa84ce93dd8fe4e61d2c40d25cc9fa634e811eabb251dcf059a0c7f57d71a351f9947d5f747d84de05b292f7ad998baf7537b2e307342563e4d252acb842c8c5b80bdf6afe2b4976db0442428d4aaaa2a9add6ba80762df80cdb0ce8c45d02870ff539ac4b5511e076be325923f36f38aa23fc4dafad2bc1fe93cf6d0b20485292b1db26e8459a7ad9908d90696c1c3bb59cd807bab26716d5395c233491fea3f4c1b67ad5cdc11fe94b86e4381842c1fcc718d8e3b93f2509475d479bd245b1eaa3a8925ccdeaa222739af27f78e29aef775ac9b7b9546c9cdb06544285c2a46f0ad05b806c78e68aad8f40687fe2eb7d0de8fbf9736756f96bbf7b82278b1c89ed5fb73b337a7d9793b0277165280b35f5ec75272ad67f60f709fff4e9ede1a0c2f1163b3425779d3f7b8ab9551fb21a9fe9fe1dce1f5e743a35cc901e2909d059d5886113057ece09b1858bd1b0b36e847fec4a010ba0579b5140ce78eccdf0401c253c0d06798ce6c76ff8a89ec220546d5def4a0ac2739a8f4502152acabf053aef56f208800a6cb6929d197c7fc803c403f0ed06b1a1e307848b28cd07d6a1a30a262e41814241ff4773ac0bb6cecddb596813e249640e99582126ea70dd0474f011053657104b00655980f4a33ee4bf587f3f3440329eae4c14473297d1f497022bb5105a6c9f2ab2adf581b9c73c8decc6d95daca12bf117210040008c06a7be4a66d737e7a010bac1d1f395529e2ddfceb1d9bbe810357708fc4efe29d50b20194874fb8bb678436656f909b659bda9a4e1f6ab182e25064a6e8e9d0af76c21dfee5bd0f8db82341f2bb8ee66021a8feaa565507e1124050dbc914e5d8600b5dea09618a32acb36bd2afec34c5bb03d69d65690784b3edb014082cc01cf43e8ee1758416a556387d7bb75707ce27f90115bbd5b9a1509b706b99c73c5abe36ff19cb08e3a54550fceb737c4bb13718ca09e6a7e6472de11d1f8b567e85f0359e3e25ddc27e1549345e1522882885c1f8da0ecbb284377795e271c3d386bf6f0cf7e845354dc29a86d15c235db5b9e97c6dc3f962ed4e1b0f101c85eb41484c1970ce7fa1e9012d7cd415ba8603d43f6a49330a37f88b297845c826bb1aa497af69fe17946d8a7b09ee6a583d951575c7bfee5dd8d0c5f2999c2b09548c09b476b54e24b591b3a1c669460418ce8ea6cb4e3d8820e7eaf28b685293b3bd8a50246cd87a45e22e03c3b540c3d70b26d75dcfcd345649e4e46d40b9a960110793a240749c0c0cf2f600a7a81754b35c0f400041d84132f2f3d269329db2b5c91ea4478f71163c7da816c0056dbb16cdefc592a7c90a9705218c0b33740774d70b4a10a7a87e4de52a0aaa7c12446a1710aec6a66e67d9737cac260224cc9c707520b61cf6a3c5a8f6db362e10b1d01d506ae0797adc1c3a975792acdcf4ef9a004e6d0133b6e1c7d6a4005ff6b91dd989d7a110f78bac0bb307697552ffe045e3dde440869a537801ed950e6155be5c6d1012df54bafea9b68e5357837ec259db89a99105b44ee84f5999696ce10dcf8ebdd91c9829d102dc889a119466551be16d01b5559281cc73464a6458de84e9002d7ac1506557d59fe4593834c134d599c7095926766e18047ae322d5921c3e83c6b9d30c2eaf8e9bdc9956169cb2d54058e1a47fb3efa44238deb8b9aaebcfe085201fac2b913e412182ee1cea6b0d8efb1c04872bcbbfff5511b03b2a68d40489778b77f9962b813b8cf5f0243a2f61d8185f2f4a57bd801da3b4949e30e227aec324c75fbf087d145f390c15389d2422ea6fe5c9b8fc7b4c7dac8c1bce59a0bb132f1982c8c5c9acb91bc6798351358432217555e2e53ff8e32afb0b1b4eeca6fe69938b9116de1d9e2cc5d34efefa44337d71a0e99446acafe528c013251746f28f0e1b14447dce1a1a6e590073751444bea725051c80adbb68945338cfbdab4b7c64cf9e9613a26ee0d8aca0e1ee2882a4ea6dfaf842ad173d64f2994c8ae8ae8b280a0c913016299cb2d5ce3891f436583aa1a14d32bf6191238048853ab58798fedff14822494ab6b261487de093a47567d3e2578511fb3e1ed12068c1b12efa098531b8df17d367b79cd848a811a99bb960643639ddd707bbffc8443363e53c4c1899b988450331d2a31188f224fde5183c7fbd0698587f29a6e7a4486f030800020319d2c31809dfc8aad71d9d3ad8c9254cb894f54bc3debbeefe88109d7bd19f566e7919e931497bacf4072748c9a253ac2b8b22c56aa0192548b89d0189f2f99691456d4390a181c9d0fdcf839ec03380502da5db0177e320a67034cd29f62caf60c7ecd0af3abea4fcf321272151f008c955b5f0795ca144c816d7bbc822a0ef5f41528532df0a6f1558db7fb9fef274aabae24038bf86d78d2e3096fb70e21d62d740a6d01ef23e851187a9caffd0c8c404618463a79b6d2c7f1843ac64f76a7014bee98e42ae2a8394e1e330b015342f47b4b7c4e3fba616ecbe64e9dc0eedb926b95528b4b4292bdf2b7da4703c5bc9bc3a1788c6d3728bc2276c8d6faa1881a16e8f0741becf14ccb4d47a144dca35af3cbefb5bf4d56e41de0214bb0b9083c06c9f4b4fd489eda086e5c8d1f00dbe2fa669567c85f0c7cb9f0d3fd239253c83774058dfd75e3399c097245ff5cdf332d0c352e7a2144a1af51570d5945b33070016d488db963973afcf65fa397d74a77413033d6ab2ac98c87bf3c6a4461f51ec19202a60892c9552757399c574d60f89f844ccb4525c9f741c58652366f263cd0c274c586e0ff946838388170550b1356167a427b307242cb458d58bd0f9709f76afcfba7f0e13e75bad5830765bc3852b248ef4f0849774f961325943ff6b955a57bd9d7f65a0991f57c22d68615fa4b28e39e5da73b7514580bef95716468d44a65e296190346b0544c67f2d3572eff4bb0c6200cd9560825cf6c46f5f9c53121da8f7092051d1221277c14fa8971ecc6565c47d520e2cb066753eb97f7f306111684411460cb2d0e2dbc7d69cbf7fe0ce6ea8152ae3d2c01b2856a1c4f0b103db9dd86e7685dc7e7e962b02c02c77e2dfc0b3dafe8c80647392215de8310f416731c29f295a43e171be827680635dca86195e070559e05931d0f0b219ddf879f3ebfea780b4dfa36fc6e1278e429c3216c307a16066c338a6eca272d27e2403c61ca3db645c5492239b76ee321534fc2d9fb45ee9ba4fe6a44fcb8bae90be2c4524e12e039ff60947393e443df12fd108ac50a3489c01568523ab9a6dfeabfd8f6607bf105a4bf60c9da538af0418e6a0023ff219a37452213769ce75d1eb895eb2646bc19cdf433aabd59dda585a092bbce817912db0505878d9a8eb443d464250cae967054052e0031ce83980e11b20c537c774d0588af254ca0f1eecdbb7b1b81d1a3206195b923325550f7c4ddadb4b64c908a76934ca7b490cceaeb6bee239abc3db0a2e556ee4b723260b658929f57c3cb19bc64331dd63cf2ec876a2e2e8a4c2add0b9a540140a9af8e8d86c2f42c99c89b841d0ef2f20c5ba21610bd2a1c19baad5d458d485a80678e4b780e7a5e2508beee71179f4854e4acd5d85ec5e8640b5724dccffef5bcc6f1538d3cebe3973066390bdf01c693c840b6c16233e2fd75246040ff72e07d2cb4e51d83c8b2b04ed1087f8eccd22dcfd6043a2c3910003685e1fd873f1e8fb41ec176f1b0590dc4619adb80f2e514deb7c98b394bad4f9058c610c7c2eb48ca7ccda17b482afa18412b810371021859207d32174fa1d607836e790a9856096793963b08b61bb5bb953544cf95e1b6ed3fba11e59ec66c84faddaacdc4a37f3eb75955ffe57db53b08f36579c8e2558b08f1dcbdfb236fb5e87e78a2dc241f59bda259dd59e7ae98adc9a56c9e37960083947e5e26d40bde6e942e3b0e2803d6bd74bfb1747f493468265c891f730f1913047a26a4a1230e2aa1637fc3f97661b808865694229a5a987756f2844cae6bfdb42725a358d8fd1161551126e388d45e1b0a80bd74672904122d52806fada43f427adc7a942f28475b6cc8971c6481aa6b8fe7fad8a6b93a299ebbb9f6a7c42750109ea369aa52645665e57d691b5b40ce6c9b10f35dff87fd6d1daffc44dd07925c025a99cf522b59798dcde31de1b598cd536be55716479a15b325102541f49eb0adb192b494c9f24e6150839482e9afab350fea69627b07bec0f004aec3a60a3f17880f9085d0e259bcc38049cecceeff2c48959480a3af4dea2c8647ba549951cd3acc85b4b344d3f6d489187a58aba1d48205d473e99be3254ae70e4b6c243d5f18b57a3627dc63b81aa5801df92ac51565b88040269c27b6df232bcf756410afa11b485b1635cec8e56621e4e8efcb619841404a54fc44f83ecd84799d94b7413aa99fcc46ca26b1a84d45e945acda3bf49a6d713d284b0702c892a750ac271e2709b167f61dbbe0828787ac1d825341a82d52139593a4457ad23160ac6fcef2d2c0c037c84acb64ace91e2d334b238708c0539fa13140df2c340e4d7984b4f07800724b5ee370615693b59a08d644ae3d6f6b7530965cc029532b1194cb6f05ecc82d60c6c5747c126d75b401a58fe0bde1f72f826771a5075ae8a5559f46baafadcd18ef1c45246a637ed5af81300771ed31fb416bc09bbc517ef18a4467efca4336899140d36c424b5fee95f6c51cb209d6dc6bfe6338991f67fb8502547802c05292465fb98d577c0fe65677b4e1b154e87f5934d33d7c024eaa428d04439cae45077ed007792f1b087dd837bf039248eb421b1f6e875323aefb45db9287a9656ef382063fd80924155f90a8caf53f0dd86fc39bb2485f5435b2adaf4e25025756bf5cf84341ead1c0dd220b3e6d74f58a73019bdce1b0946063e264b09fb014e7740c478af5c988808ae7357a1cfc9a3ea956bc344227241507c104988bd082c70aacc754aeb1cd221be46d867b17b8eac6df36d13bb6375ec8effc6600e18d539b002d63d3d895b1acd9c609106f1c2e7bd1acc00e4af1278ff12800e0b65130956b72ce281303f170a776c22504191879892509ed50ad461b809ea46e4b74f6d7ac5e09a2e851efea29e8e6240ac293943d2c302398d12f427ae7c1cdcaa7e49f7a972e7b2c48eed654a45953f4fe515237858c520fdae6471f2684e4e9b15fa84a13287d6b09adfc1692aacf8da385f235f267f3333170c3c0adbcca66aa6ffdd8ea0c882d8b9a7766a58adb4d867f0ac935252ca9fd465ad192ddc54e5a3be5320d1b290de3d724fe76d00aa40b6239be8bb2508454a221195a04d3c951b8d9026fc1a00cae19dd956835fa2612a5033431f08b6bdd6bacd14236a37a1357bba8a00effa4715b78aaf47e19bc6c1be110fd48e594560ab1a79b7ce944fc8ace031dbdd99dbc0fbacbef8a50cec87be0fc03798eb7ea461d226a392b5c8b446b182c8f001d5fb73f3586ab08650e5467b11aebc30fa2e1a476b588a2c5e9a1c02f6af40f9da0493b518974286c69a28968ca0831e5d26be3972cd5110a8eb1833d8ca0d17d6ab194d631fc396e72ba17e450f4aa5240ef48e28615505716e833c4ce7aaa012da7a6a3c0cc2d007eb16326f730d0f0bb4c00c6f074232853bcd1be77208f63ac4887a3256e65d397e570328bd2b9ecc2245340b9c24f2b62660d0c02552c8707a34830634ff8424ef00b0430c3a4f4bc17764933d802906914dd2f0c2e974caeb4457a0388bd54af1b8b6fdfe68248e74de0128734135f9e110e01e0c7a1a426147c1a7b427dc8c2add980bfcda42ce52954e026bfb1d12192897eb974193fd397c7a20036c5febf3fb0c48011bb3a62f8edd3323545cd60d1aff6a139521a7ec36061f4bab3ef03ae0e123d313a3e66bd26494102c345c42befb73c9ac1389532cc02aa4e3f9171f632d68ad62f3523c471fe69ad0bc121a7a1f004a6ecacfdd0602f8bd9fbc51e5659e8f7bd5476a5ae9cf3a86065790e10d67e259ac15207453c7a01d5150eabb4c225e39dd3d620c1a5581af250c07f7744bc30cbd9a0d7fca7b32a67bae1693d8d4167a268077c41b58b3aa6d4e5fc8a23f4cb68fb002028b2ed6024d181bc68881cc1a886c91452c04e18521d5804799e8cbb91d919482c6f8b63db2658888c28f6b982f8dd222a15c4948aa207af54231046673b4ac8ae62ef9f4c123e3c8383540e467e99722381e2d24f30abcc1ec81861753b40b03edbcdf6dd33898dd221b8ab810720ffd250a8b0d453e90084cfbe54526bc9a0324b996d9a0a9e7117673c8c513533d779c9cef7b365a6fc795c6773d17105fa0e74da11e6a697fff81b877c164bc010ddf92f6a4ffc14e88eb26ca0fca581c50ab4c9228cf16e2e490d690b50c9d55710c569a1e3b3277360c3c025585e77592c860f7fc57394299c714240f5a6170f96806e4c32bd4d491acb844bfd3cb60c1cbf544f63478e11ef84e188f17223fc38e777533c4d8074d398e7df0212f56becdf11d9e6e789789e9b525dc21442265bdc814263b86888a9f69381598b5da0e96c184a5e1a40d3c28a53b15b790a309cd9b339025be871244426d7adc235a4b6a0e1685e6ab8ea3ffebf6706584d229d1341bce001a1af29ba37086bba576f5d4ee72c956a0b69e4bd7468d00019e21a4cf53e683d1684a132e041428e1fd150622c7755990913c3a691af3a1c6f452f282c67947430a47fe36ee5fea728ff8a833a94c2b3d70cca7e7c28214220626a4427671507585a7a8c3c7ba26d4db979a8b902e0026e0af4df6da9b88b0758e7ae19394b7e88054bf32a6f14205552e3eaba4cebb2320deec7b9167777b2e9a9bf8974e052b2793559a68c9c6c2a76ff46bce8a92d8ee05816bebdd52bf1eb1c25e9af7bb06cd0f0468c895bbcc6ba6e250e0179ba0dcb7037db6820f735d34aa2734a2033d984b329d71c9a435be48ae9378201f1b5966998921a8e96420d329240d3828bab612ed8f080b937ea54c6a5442d18409a6563e0abb65c61aa81222476b44e70cf42d2c507dc7ab67752944f2d2e1bd9340dc42a2fa43d3b5fce8cdbe12b252ac20bf43a10a6507108914ad34f6068cf023dda2105458c857f93043bdd27d6e8d4945a286955a5084fefdd61a441998f9fd981c1a50ce631657bb58bef4c9defc912d51580cd27775f160adf77e0d8788a7fe2ea3d7a0c3944409c71b45d250900edfd12755d26ed15597629e2bcddea792b0c6a14415f2feb276465e1c762dff955a62a9cafbe5619bfd3bddb1769388932f1e8180dbf4c206bc0a1e1082f4971606c2d35c1bfe889a2b653e4ecd65aa11f321913bdf16d724c7f3bfe319d2b2b3d6d8cae75d3e1592fe4031e3374a844656c4021a82cf53652e6c24e1d0131acf94ff1e8af698a748afff04001478df1038d0f070c27638b1188e4314e3cc6ed5669a297adbe2308b1fa30fb9e47cf001fa04e668647d9591647956edd090f67533e59f6cea5b2846764e3a6a544f7149ca838b0ad380652f8fda5750585d19af29f0930888f24837e24650e2e9cbb61a63187fcd3f0aedd440fc00ac7bf4d6da06c3226d80ad1da6d8c32fba842f68831bf2cc10c793673b4da53a9d1f0a544e91849f1ff67b3819e77bf9746a50299ab511f8a0131fe1b6a4501027018d4dcc81cd02fa7e168f0200481fe6be6006f1f878e38ec0a8f3640f0c912dadb2d8cc9f32fda5dab9596475a20218d09ec8f38d6f51638fd35b2ea7cd1e8b17531449a1ad1012979c3b90985d0fb20d3de83accef1b5db1efa843932de50b845c53a8c581ad53d1795cb312da0eac78203059a40279352cfd70b1b1e24224dc430a89af05f94847d6a42ee86176c43c611f9c159ff8cf1fe4f7bc6e4874fc1cc0a18af393de6299caaca79bc9e0204b632061aa0e3edcb5d6c2c9c925ff2eb8c891caef23cb9df9b04eab5f96bb90348ab288035675421963db26798e323ee5e99c433fd8a6a8c4566733ac21f50aeea23a1375baac45cc7bc39919c99e37c879c4f1e616089bec30a372135031f8dc3cb60087b5a3a1186caa1aa544cb9c55f769a411dab527eadd8d1d77b2eb24187909d7029d053d119a9fc66e2d683e09b98d5151cce904f6ce2cea1d838974abf9dc2d5c5e128ca35bfd6424b3691fd9d31396604bf2acafbfee6397893d492533c2e6c89613c65752dc408dc9cb88f975583629cdb73405cda99f2a225a63672da4c5e5ac1f130d76194d50e0fa9dac13fd7630f375c32964f5811fa7452364e82f890e3196fb7972a3df14616df22ba530edd8a8f86379e6ccd5fb13a35d5886f94b3392f1313fb46f4bd73487d8c7b88626eb8fd6e6fb829a68295a482f7337a1d2fd30af57ef58cf2e3f9a76cc0b15a2ab6dff59512d35604fbabc04b6b61ef318cfeb21cfcd218ebedf61b3fa7e9226f97a70fa1ebfb03559c32309d3d8dbc4a09d10053977f957054018555f16677eb8c1fe9b9a962d88d070b1f8750377e551e45e4216184205f1ae3e7347717266cd5d77afbd037bbf6892fccca4e24577261db10049227b071a457e6a1a21ea97dd552c2ae5336fa5affed985867e8bfaede0577c8892b89534f4741e07eb55ed8550a49e0ceb215d59138ed01748eb7ffef6646745a1a16a988a05b2cbc0ae01707403154ed4fbc697ef4d4ca23bf17c3e8e0dfedec633a1101d469234b5499ec64292e4d8753a0397c90f4412abba40fcefaab4c243a17debcd503081dc22104aa6105e45a92f56aaaafdce4326730d741677446907225dd0e899b82299f92c0215fc922dc7d2520940270b77ea9bf53b27f47b8123dbf34945b1433ca2a35b3a044a70b12b31e359a1fa0b2f4e3fb43974c54efe653ae85b487845595259907d3882919cc2f578826aca3b03ee38f29890b9e22f473522693b38317309312e11126bb2df171f4a01ccda68a71bcc6d220521319a8af48fc71314054276a2751ba93a2c8edec3e7c6906d4f39ef87742b0fe5fdc5ae218ca0f10049798c4ca546b686269887c729430762bedeba899b48077dfc3f4018f84f5cca1d134f91795027104d50877e0e2b6b46ce5b9737d1260d754730bf61bded09e6d09393f14daa2b6f2f8f04b6cde24473263b4667003b797a08b2504f2df1bbfd9b5344aa79efca18f58422f1e2dbefc66d705c7b6ee4da7685c52a58eabbe9174495394a3d1a05e452fe32fe93ddbad04fc0f9387e5186e26dc966455f9e3eb901ff77aa10b2c98ee2a92321e856c90f2033142930573bf51c88e9e17e22264c9cc8e0fb41720acbf52b42a2797a69a629014313b897a0300c456684583757c656c9a73e9c33a718e20cc91d9f0a987e241a3ebc5e8a4ea16fe2a6d495df40fc8d7dcc269af881ee67dcdcfa4597d7988c914417f22e2b77180276ecf71a3e3cf68ff998062a8ec868f19bb3d64028a484b50182adb75525a2727c651a33bddf9b27c85260ece3adc6edc41b2ef1a49c795b7ad6350aac53507494774498d9775cad2f4ebdc316310dc338e78971abf11d9f38d6eda1757c6295599502edb0ceb94faf7a7e6efeb69485e83d2e1cb8756368c36dc76e1a398c48bf144241ef9ff7cb3d221b8aa81d0550d08ae6822704523a22b55f33c329d7291e9ebd671c7e2d8cdc2671cc5b1d22c80378012af1413c4fa3523e8a709a25ecc087aa34ccbcbee7774eec214396e366cafae71fdb8e1f85bebfef24b8fa4fd3c894dfc9dcf418bfd9e0b9491069189e8c970faa7673b2f8982524a68cee9227929a5ceec5f0ab76a8fdb1d8f4a2dba9dcf16a58afa6cf4af119dacb5d3e81af22fc385b348b207cf33b72b62df6120c80b07394cef568be985a6316b9cb03589b62dab124bc3b314316ed9f0e67698023af117548b8ab4f97809f7b92c74d00f42a9f422911c2237d2441bee4b8a1db2e75064b25b10408e513f2c0e961eb9b71cc725be89585a9aa49f1d7e69f4da4eb78b887bb484a091d80a9c537183f580312c5092696c49a24b579257629804b83528ec0c987b7138a3566083c8b68e1c61760d210dd1fed46a44bccf9d836350a1cc05e4689cf66f2a2fa7202707994305fe160ae2a0ff1f4dfbf3f1a9850f25077e3cbb481c3e641e0378cbd84340fcbee6958164d54daa5911007d935520aa49dd32e6d701298d2b840bcafb4e80fe28f86de562db14c28331c8524343fdc0a954c231d762790ab58da0db7eaf9ec128840beabe32da7e94e889fc2f40aad15b7ab74f9c21d112201425dbf72ebe2ddd3f9bd8ad8466eb1716007db2312dec11d36288d6d488b074c86bef19ebdec98400c702d1425dd84a0521ec8f400545dd7cc29c69186d3caf9a6f3b64ec51afa8b3b3178f84d95a5f770634acb69f5d6c2bd9ea071390a950476e29b4029403811e22db8a3fcb8f715476a536c9898ab6adda4a36f5c982fcf08c7e970ac1d1d748409b5e4c24445d604d4d777f7480f2d411b87728954e80a9ec5ca9bfdeff3a62c79e2deedbb1920c68cf9b6b4d36815a568760bccb8a5dbb4a1c76953aed2e8a4739c11b8d28c0099285669ff2bf2958c4d50b796a1c4e784801583f44edc0d07dbd283c797e66f460478fdaa9ff4ef1fb0151ac02066d4fd4e91e70b73ef0d08f651f3df744143e30ea7e60641ad7453f7e04422d3faad0ce8f0d927ec4c54b9d2afb1114747d0e81983cfe087383bdd7fee57b95e5e522d3f5d30d319255a3c1f5ebaee4e78331460cf4d6f130c463b1f76304e497142300cb26aaf120bf721e85533cff2dfadfbeeb1615ee06116897e78d15fe2321c4e21e1dbe2f1489f78ed3767886ada8721f0c43c27b7ef2ed1f2c4b6c58bf30c2e5c22e363855b90ff4a0b06e25551ef0146b721ee4db78b21e0d32b46278a9927a3f7da6f71baf67f6f5b99a20c4bd9ad9fdf4320dd82f5ab182cd0cea820270057c2536d13072074e9d6c8fb5c3ed7ba572e4432fbf4c55b98c5034ff49a8419f9e1c7d502ae1d96e469610ec7683e789ef386fc2b7f699f84731f6dded357e1722bfa242e5a09805121edf43d79b08d3b16caaa5aceec784fa6ffe5204ed66577616ae259777a0dfd9936a162208a286ce27e3939940b89b629b96562a6434132e9640a40b82ea8de51ec2c22d14fac09bd1b07038f3b06dacc97ccd9fe0c96f102b0676494e674b6d82f4010022b70279ac752069b3ef88cead4a9fa60fd5e03565147e7c1c1a0def5f9867b67295a3f60fe9315b323431a39bfdad21e2079f483976dd18205a160efd821a0faf34ce14aaf9879cd67920befc3dafe4ec929674691c41935871c286262ad28c69f14954a3a6b7ff9ed8dbd9cd3f901eedfb68cb9bc2a724914fd2670dda5b221d885f47a3ba59e158951cc17041e899c0a921bd35ed11f11efa31e5bca8cc74d5a369ea1cbbba11d5862d19888cd21830042fa86dab0d5bf22513c9632ac7e3e02f4702cb53fa8343106fea5878b3657258f7d58bb171101f93c0de2ad594e534ef31059345337b50d01d668f9b65d2701ccbc126202863aabbfe1cbea5e713901bc1236365e8990e09de1594a21f6d3e28b9f9c877e62927cab056cc53cd488e405a61922aa556dbf8ee6b0439b20beba2f3856fd61654cef07e325b9a35665f2c63d40222506cfeaad86121944a1e50d8899746cee32e16f3c55c3db9495b0badc6246f6afa9541b61044eee7e349ccb4aa895ac29de68c2070c4149dea130be9b608a3879e0fe7d43df45b15c311eee135e570470f245a6eebd751a522ea6ff9d13e446f567a012342491c4a8db89b9ae766c952047dc93a6fa92029472f6020ace930b08973d0caa7941d57fc9f28ac72a53c7673435446ff674b7c911430140b22def347e524ea7162ee8ec491782a8bc55f490d1a54fb469990d640a54d9e433cdb89381d2e11744ec546e6cd86512dc24f0f0bd497eda8001a8a89157b55bba60965cbff9ca8fc31ffe218f430729592215078f9d49c0c27fe55d72b58f413d2e48644f0c47392dfaa39861aefc9ec3b923c40e75868a95773ab8282d81102abb4ffa63401f148133c5d82622c8826f099ada2597d536e7d5a9a5095923a82cfea05fca55ee9a664069103fca61b7693a424258abd44f7fcae18a38c9acc52abc4360d705a0ee71df2bf35410b6e5b696bc139d73c593c850b2bf0b2f38fb0e69dc48d0a19a55488eda6c23d2367a0419521c9b62a5d9741c0509752ab0a72dc9d85bf7455f08756ef904a21f4f20e3e9b3861e3c8882979800540b3efd18520e84d28bacb33f37fc1105e4259afe6ed67fc0fe1008ec182c337a9916e63323d769908366672981c7af9b687105d9827e87b959bc9a1d48d1b81ee39ea96f6c31daa18103dc054832c15253ea60eafaddb2ae9b0ae44db8b32b4399aff2332fb183a7b3d0dfdc5c1f181770f444a9ec94c75ea2df3bf8b7d68421d9c6378e62306123cbe5cf8f0b7df98053961958495e4d8345a7116fbbd61911348308047b139207629db4740d48e37ba0a860ec6dae0d39e77242552a2e5f1c1ddcf07c28a527b585135041ad9a28c1917d1321a52543d1a5efc5840ee363911a50c29e22d8e0d6c51ef11f22e33427cd6e4848866cd8ae2a697da2347b442c51b0f5220181af85136de38e71bb0b347f16a2e79e6ba50ba37b28f978c4236be6f1a34a528746462aaf01d3ccb0d510d56e3650f8291a3d9d6c8accef6aa6a92ae5adadc28e80ca9a60fea6e7a610a629022fd7d0a73dbd4a045fbd598aa162786e3165e2b460bebf49d493a951eb0f055f5f62e000edfaf585ac8b33c02fd8d6386a7a56adeae00efd698e8ebafb5cb184da38ad8cac00c01d70721c6d7de527c2a123ab040e26fd2c8146b587f6251db85ae8a9a0d47297fd86bcc68de64a418d50a21dd187500181d31bb0600e46826b1874a3c9ca0078d31fbd073d6056b17a2bfb25e3aa5f2e7c885f6c7eda4ef591f73cbd244916237eac3a7581da04ee579418a8467a1906c5923759d485f91288ac4a6f183ead0c78d4c8ca8a2406433d7be91ef150db5341aeab3f996b263a7ccbec414d6be9c8c54adfb9a579ae9e456423787c2d617506a879bb54b63a1effb4da85cf803de5e0d1eb951e4915277e7587f6d31a85914bd9dcb799f1eb36b8adf0c82e56d65d8f144e3940d69760052d49950397336ae208508f947fcf090f3aecd4e768635746799dbeaff72ee9c9a592d06a4aca351ea84a1091eb2142b3af1a6702ff992595940426b988cc3f4528c4d78dde916a0b0afe478730669193d2396cc679892640943e06c90c882cb431fecddfb95868047a3c1e0d1c2f80aeedde0ea00fb7f5199d31470135ed666a0d600ccf059c62c8e9a2eba80e5e6517319de8a34ef64a18972c6d05277ad592ff1244d327436071d95abdaa6551523ef9be5a2ceaf8349d6e8755854ba6f6a2721c8b72b7f551992b0ad40e032238c0d3e6c9b48c96c0b15237e10c26ca29af7267f41822c89eca583d84e45e0da6f292c5be1e7460a7c82b8168de9eeadcdda52b84599c6124c1458fe66dfd90e7d5bcf21a39b7de197be60897ecd5a24ce11fc3b798cec02c570d6d8b276345ea83ca09f7a73148a1cc376a228933d7b2e8f920db77b297bc606c3b80f247accc4c803662ab4521a6afafd17225d065c3a36174290d6220094696fbcdec43dd6762395afb05409a49f560d0d5af010e63f14dae4d4439378d387d1f8f3b73e6874654c445974558cc9abe2220278b4ff11e0fa980969281ecff940b7b5bed97693ab3d611ee7ea6f02a299b510943647bc88e459c732160b2e17b356dff9ba8d1d9abe473c1355e2debe11376b3b74d6974070b08aa36987094663c6765c9ac30e8aacc06632dab0b254cf788d07a298e48c5e349a2e029224339426b0533c28fa3e936e94bdff566f928c8262854ed51634a5fcf057710666cf4c295aa0be66da4202721f81da017c8f45d1bede6affff45a4fdd747726a56289d4c83399005b9e6880203bc3f72ee4906c65a1added9fdff833d4f2e29cbb4b00b2d82ecc899719c15aa7e590c13352486050f52c1f7ffd3e2a53db6109804c187b16334ccb41928a5b7d73a17de46a0401149cbce7b76c278161c4854cd3e53b2dfbd425f7b24b4a2e7b14b2ce3e05650de3bd1afc389ea0846cf17bbe29b3f49aae1be2fe76e6bad5eb540c082303cc94413f443d2fb758a2b31518846dc0eb6dcf624a686810a7c8bceda996ceaf9a0a73bda7184a3d9778f29de49ea600e6db5e4d186d7a6f5ad23a64816b8939f98c0c51750a9ed6702fe6e1e984bd99a3749009ccc022a457ad7d6601775475ecdeb7842c48201e901fe2182cc7e99fba0a138feba438829145ecd33dff0636a854d28daab8e63733950be790baa0baf7f0c07f074e37c0ca746d3e7e85d7ba030fd8048a4b6077faedf831af7debfca69cf6e1b1435e15af02e97e402e80b10ed51f285c13130b25cd71a5edc96d65747082cf1e483581b646d648d6f9fdd52c07ab73ddb5b49861d0d69b0407f1d8336d0a667c3d9ba9f1d60a4a83b4332c0b4a93e793ae6469b0448248939c07c44410c6d600d8bb32d4ff95bd55f62b54ece0c4ed9b1d0dbd4ab3362dce6374ac4718625d4834d6727fb6b631cdbc4b92660e1841f5cb4f6a8a560a9efca31a8622d34979394095bd34218d098bd97c2682831b75cdf8b4e4a0bab6b6ee8a121e4b123599f787fdfb2d30af3397312752608a8c1a8e873430064c1de86c76fae391999a170d90206b54c8de3fc2da32ff89210965a1ccf7834d181dfd29db5b8937ed8c4310295ebef2a36fa1728d59e3327ceac8407038aea78a9923e755599828aab4b4b222be82ed86230cf73d9ff961b1db6cb8f5c7e59f4001ebb21e9998e39fa81c070e5545a50624e392d6b18a18bceb948c2fe3903ec8515896d5a03017dbcd8a24f98171ae4197f591b4f647d5891035775ecd222ef164aadbb2853119f0c9a6e7430e8c850db7aac678ba66a20e157614ca759773d9a78d109b7e22c01f2dfbf1b5eb1fbbaf4fc18708d6d5cb6c81ba10d347281e8c50876d78a3a7b1d66f948992e3c374d90649ca765a5184879572cbc94401af3be97264c6bbc57685546d11b8bb922aa047c00e90ff625be7209dec805b56a2dd03f4f1ab269b5c3162d8c7188252b20a5194fe5227621b03b82183c7c200a929d7e4b0d5fcd5ceefd1b2843704867afd04d935982244161b75e5f8c3fa8f81ec6cedb8bc255578347f31ad380fb53bfa2a64e43f3cf2ce9a434558af01b837b386230b8adba366ae96c4ae8021cf3d5d1fc2f822f1d21892adb6abe0ffa6687b57e38b57300d74e8f9af441f67ad4ac5fbd37699becbee2a12601727a65a5a4ca11e004c54cec2892deba9ac8e5385ef818a8a6bf72285f10892211cc6a82c731e98af1d301762033104cb7086b158334401cdcf4f3480d9ac8922a428914d2478f3541c252fffb09b1649d6f902cec9ae6df9455f5b7fe0eb93d34c525bb7a8dceb336562198d287a8308607901c714f658c410595c9a78b067aed89ee90cbe42913d5387eb225c3d716b05a8a8234304dd09df1115343b51b0cd24c2552f76e2d2a377d92063aa5beb75d9665a299cc84ef89407b3aca93938ef20df4a9d38afe99c46c857da18e55916266771003be069bdddf7c4f0ac00207a272da540113f74548e67cd15d4829918ed9e41415c7b059f41e1ff046e963628b9f4659c7a4ab13b58fde1770381600a51e597620a19a5f3d68c6495a2270b17a1283a418f2f0e333b1193cc7b31a9441a9354b929a2053db12c3c8c0e06a3dbd26c3a90bed22e68f7652f8106f08247a23b6cc766dfbda7543c34172bb3f83ac5d63ac40c623a639b2fb9bb574bac8a7526d2c476cccd32bc4118403e23fcfe4b973982be14caa5ba9cd04379822000f38597724aeffc11ad258c43a805a219a6388f2be8ba8e3d9d8d57bdfb1f8dcdb8e85fd72cd25bb5b5062e113a4f18736d51ca576e1d92b478e74cc174759df5bd742d1727d95741a55dd08e9a8083fc3d2995e8eeb3263bf27a44f8784dce8ec7c795dc52ea7626a9094852e463a131619dac42c3dd32546a28fefd82c63b0b941863f0707a7722ec7c7689fa19d9282ed6dca862c8030926c298d93d058904a1b33490002eda087ffe6e1fe69866beea11a64c847c221d9c578435b50a8bdcfb430dbaa31b42a2bdab607957ef6fac588df41a56c9d3e0bd897e77413fdbf25747220052554d239c4d44eebdb8c45ee1834e19f014502ee2f85fbb9a5c9bced2dde010f2cb88ba55dd3ef40c918af09e522230d0207613376848703ff9a8b6f7488206b673865783e5254f5e4b80ecb743c17d5ebeff6ac659a84755ee79cf65d5eb3fabc6970738e6e1a3be1f4062009895150b0af06b0de49b20a2dc5354f5f5fa2706dbb3c036d8b2a30bb93cf707f5faa8a2481319121385798b43f6cb4a3653d9023e6f27d68e4a93bbb4903a12a9ba0ba040e4e65480446a7d539f900e60b6e5e7d8603c133414e5435446b1586cf89e0796886aa1a61d327f3dd579957269ecdc6ef353fc273b5fdb4884af48bbf0b7ba039ede911fe763a44b27592a5a2d8ad8aa0dc9c0060f80488eed0e71dd19e9ee6f0419c162f0ce99768bee94a08f733691ab7b6c298f8ffe2bd8e163e1c4238eeb09aeb389f42c56c65f4649469092b097354d4649b6fda19708a7ffece71d14fb07f4ac02cac9f95e5820504a4e07bdbaaebbdb6b668c916e796acf0fffabb999f242c76e68967201d81a2144c93944fe6d83e8044ac83d702a25b592537c06863a135404f5c723c10434f51176d0dbb957171ff4cab728d4c8eeb6e5de52a69464b1052706c7052f9f1e1eeb6ab1c06fe555314b34c1de71968fc62048356646de95bf86119ddeb3bf6fdfdcbe887a3dada133bdf9598959177d93d10c3571ee2ace1a23973f0987d47d5d7e522386774c6f67d62acc8d797bb8d80ac2ce66d6ceecac16c20e5dd4fc0004ada3a687175754a1a6c91de58dcb9c418d0d14063528a046a82132a74893850c69deb4b1dc954f69d6bcb8c289281d9e4421f992849daa24e0503049987971c5f7c9bf3d72b7a774baa5d2fa01f03a718cd1905ce8834ef6ee49b9393b8ece39275d3dc18c82e97d29a59363666621130a13f7749fcd711c475f2f70544a1da2823780395424d21071812a16c99399b99bb393acf9d5c5dab2fa93c5fc404d54b97fc2b84032d9ad664e0602659fa28b407cccd1ea83ad2aed7e4292fb99eb6d3aa9650eb294a12c9231cbe34d1bab37fe7de1f37e22302db5acd6327f30fc1126bd1751204e0935ba5cf78af87aa0b01ae0367259b264c94d35303133cbb86fee1694ca956938588707ed60fa8fc5985b5c25b1726a89252232daea97883ccdfbfaf200f4bd50ca03d0af614b10b8eeb8bbae3beec9ba4169d09e483e23e9b5da1ab31fabb726d56ac1d70961ec28d1a480630792d3240ba9657e9321889b734e12847094896788cc4ae966725f0ef22efd6f364b9f2d4e94f3c2e0a586714179c5a46cc2f84086d0811c9a6fc3e78e3eea0c2f546b73336a776daece104142ad42147248b1dc8f333811ac53d871460d73b46cca68a291fbcee7addf791ff7f5ca3b724f55e09efbc9893ff552118af7396400b5cc3dfbd5523136996c2624a3087c59cb95c323b312ea2059149bc367404de44ea6ca20ea79e2b8c2ac3f2a90d2cfcfce6cb539237890d0a6eb93d296a2b4a79a8a8e2d2575dde5831d5b4c8b69321de5be19fa9dddcdeed9ddf41b26b8a3092694eab78ac7c8cf97cd6418cd98f3399e93721c27f6f8c1719cd84369721b3e8ad1dcdd9229e5eeeeeeeea692fbbb9b5f3609927e9452ea5038ae3ad56ca529a3bea59a8c99c866326620d5282109470e1ddc4cd889cd394ad7980927ca6a709edbd348f51f4e3c7135b13064b299900c18de6a25a2e0912103c3a7073bfe03d966199e22cd711cc7bd1c9a3488a4126a4b4c4db330463ac89811a6a8ab9e27a250c9bc2ee8d475b6e9e4cac1c5074cd48194cd97411d1f8132c6ac5236bf1b3570290be0ed01e4d21e4af507274d9e174a695d4a077663efbc6c7777777777e79e316d56a8a58fc9e6ecf1e3525984c7052a3a5090ed58c147530d4a6c2ca5846448c93e662f28aef051c95654328f4a56a9641d95eff0a020a8680eae1d788eb8d0752ebc8ad8f11fc8856983ad86081ccf72fc50aa2d313575e92203caea675eb4e2bef3a81c5de4a249c78c9282a568c9350588a729516ee7ddc92e22f8d768228e88c75ce7441d2df31c1ec4030399d5af6c4b3e136c9d42a00b06170ccd414f4b3efaae3fcfc045b2aea8d873476e620e3eba846525ab2596fea477ecc84dabf7e71b77a9debb8d35c53ae4cb31cdfb3217f27d2faad55af7987841de217de9dc24877cd5ff398dfeaf6577eb139580d03c5eaa377f2f44a272fd8fac92d8514e2d49972edd9f9b6c5cabb97069d3aa8a160c2cff275f5cf93079f4bd9421ac16b02484efd0a6d49fe2a35e3a634a0bb14bff139f63f4afc419c7e89f89355cd1568f8f24ea7eacb43038466bf59de441deb788e49ed8a28fcd72d823fc80f48094f54f200d0b17e020a94508852946039f86629207f53f1fe5e018fd2c1beb7582b23af0c1ce4fa16524eca8ac113220879a35028f9f2c127af0aa79ae562bb95af9aad542bd55ecc177866c96bb9047cb90f8a8878cd1941353a882060a2d272bef2803fc6699c02edcb9a4a341e829a6207575a440b9f22024246366347bb0231090cccd6d051e74c6d90f3ec2968a735719699131435679919cc9bed8e59a07f9b36c0563f6fbd84af6de6c9c5d4967d6893f3c0834c28e321842dd6c7ac47782d3be9740234f3559f97c0bc3516cf3a2f720f63f2ede98d7df45a479e493402ffbf299ece1289f524a29a594526ed9cbd874e61e6a77cfaeeb260a6ab8a395b29e9129d6999ccb1b21779451f38eed2b9fdeb870472bd4413da370a09094031c67555555736cecabc45c704a8e06ee8cf1610566ee1861a37519307084b0a0ce9322a240638128dab889410d921e986c809121891ba8266a8e48e105014b49cc6c916233640aad71f062a4cd901db4d4a0cf4031841062ae54d9e0a40d18da02788041c8161a9638b9693349229420df44e1411648aef04283133420420bc28223c0701102c5883550f4149d170ca0bff2690a245308b9a3d0170f6c5186872f6c8ad819e39a530324dcc040c3113f741452de198e06237052608593106b6c5ea49891d24413559429f3b7a5664e543b4b2cf6bcb1d65a1d3a9f102e01a94677c399b20fc78cbc5541c1898253c41d654d5043d208e164888d490828ee133b3508e172a6cd0b4982d0104316426400a516d52415440b922c539e94b962c31351ccfa099980305950e1f2e486db1377052369bee85015a7091bad67b75ec9ea279466c00113850644ca29e3c30c4a3062a0017911a1b948848e13369a64a20e1b25342559832de428a10169d581018b10275a873f6868409a39059687c960397344884bc7a0059d18bcc80d41dc30415fa2640626da0c85b8d03efad4c6ce13dac715d1640b17d0beeec9091376685f055f488304edaba04ce2ca9822342ad619fee0a0406768540c42eb2776b64cca551148a09aa0cd10e80cadbba7f0ffe6cfb9430ecd8fa3bce8fb99852c6a5bdf3c68b60c77760c77b60b74ced514bd6ffe71a8c679f3b6d14e6c9bcf09034b454acb3c8a1c72aec983dc84bf549c5638a7ca344e2b9c535ffe39ad4ca726c2d244954a95160ce62706a1b9d8c95e2df3d5f34b1eb33bb158fe1124213627f72c9b77ce5a63dc80e60bcde61bcd7871e78bc0e4ce4feadaf36d13f5983bf9a82587756ac2b1b071caa7aa2b52f25cf954e5e489bef1966ffe944ceeab65529c32e4e003564231d5641379f4a682251c033356503db9e972e57f573eb981e2c98d98fb8af3c16260f9c1967dc0f200508c91d283ccac9ac80e3ed4fc3a7f2c1afc4d51893e61a3adc4293fde4bc93749746ffefe1362477993bcc8521d3b927270b7c4dfd40f968e6319d24e4a2993afe6c6769b697abf8c2e6a519145eda3ff87d252f3f4d9e2239f258db3285babe9ab7d995948131353cedcdd4aa6bce80787521034ccd683f857cdd44b2de3a91a4ffd149369aa4d62b3531237795012bb828f1fcfde5feb07a135b912235636cbe696ac16855ac63c35d871d6c4e59fc93012a104a4b0ca1826536851e4c92d0ed5182a51e4445185e96e621b3f47e1e72656f2d305845a1b3497d9bba96be0a6232751a85871c272eb0255457e6eeaa66e6aea266e6a2a3d6c78f5b28a071ff10d6359ece955cf3b62801af86ae2fc58e155f98589cbf579d4f77eb26878dd081ce3e8882ab715988716dbff23f01177212b04c3169f0b632de3596247a019fc403221a326facb8fe42e83bf797c5536a11b9b282fd8d89c108d2e4aa055152160cac70f3ed2f4a2bacc2b173c6490575555cd89735b859246a5d564aa55bbb762fbb046f0fe13c7bfdfb7c21f1cade7c16205696ab4c9e33fa00f07c12e0abad528a18becbb7e073f0ffe1f603b9928314dc4639aa8c55c7ea9c48b6ab536c69e10962f7f0a3b62b1eefe1e168de6b1a38edbff4db163953e59dfa10fca222b6915011362ebafc251b271855e38faa09f0fd80a3d862ab51b4db2dcd6b21afe382b61d27ab233b07cb9abb4ae160b44f3ad5834daefdcf1baa673361fc99b2b49ca81548345befcf2e67c5b21ab3748e2ffffff9f864a4094baaf6f957e5c893ef7ef16f6c2c378d9aaa3be6240421df7305ef60707a7b402c7713a6a98c1edb7af1890d0b3075b4c31670727555f8cd0dc48ed055559dc90868739c5ffb353a37f0c76de7dc20f0ea51c1f8486e43494343b75fb75a4b0c387920c41aec0ee935e6bbb5d5b4c180c0683c16030180c0683c16030180c0683c16030180c0683c16030180c0683c16030180c0683c16030180c0683c16030184c6ca2ba11208aca067322b00bc1206420e7f6333ea2775a1741909100041c6016c3004342ae37052040906c001f7800240031fc00740000183f1c6c105b2dbd70010bccee73cee954565ef53eefb95ffdc7accf7b79572bbed5ab2bafaebcbaf2565eedbe53c1e713b4ee1cd8ddac3febc6eb729c17a994fb381ad62ee4fbb557af5ebd3a0b28e74ddb8d384eac4d29a59c08443da723cd9bbb873d3c540d8144e3a8034eb638faa03084b0fde38b84d702904c006f4f05c46273ac337aa4589fe2d887309ac9de7de6c4da396e72eea36b9c999a53cb483941a60a314bcc8942a28a29859b0d2c8922cb1818d030f58526458b162b599c6081146eca2499f26ee038efb282385840d56009284cb422497c21630507263929686327083b3a703506192a971c96dace13704b9725dc9b72296e19d53f6bbf8d018bdb8f4467ca46b959f2492fa2514a29a59482527eb2ca0aef475967c993b759e5c090a5809403120544ccc7d1c9791c9d94a3533669c7515a100f483727e4fb98d949d69b858a453d19b622995c27528a462925d1f4fc28ab78a4e4a5db9250ac9984bdac2f71acd6471c6dac471c632fb3e2289bb99e6f4677fcae89358ed96c36b1c66992c254804479e218bb4911f3b5cdc8683693c9fe08102bc21a52266dfd9e68f3da8932cca556aef7ad6f50fecc361be78085ecd32d23cd664e166157db070e104c2f68c2c97134e04f33c921f97c6197e9cc3c1cc1587e27e4382bd095f57dcec972a494f2fbe6a3c6b2642674a418153c26bf9d5c42f4578e2a0807294f328cb95d6902ab392d490d8103991b9a6a882dd854f121065156a4363045126dec5459630b62cd111d141d5022cc152470b45421e533c6f6044cd50f777e505373a5c0b2a154cc09c727193a9f38d38999328b1972b8e20311532c3db9b9c2063466aaea4ce93458e8b0c2a207206fba8573837471e5131c2117c4a285e663295f4ad95f249596df717ae6c3c6f2cba829665ee493722f6bb5b26f19528e245653ad95ee40d0766faa98f6c5fc6a2c77e5d39b3777943710bcbd61634119d0cb8231f00563aa05713ca6a3c5c704c8620847270b7eb6b17f71e5d39b374e527ccc2387e6b47248fe0b42d193b2bf314516e8b310c3df47ec7e72c89bbf596481be8f7a3d39e4f432093c5952b1ca217f168b93e78616a84bef0c7d8720593c8ecd8b0cf0f29bc98bac1c5b05573ea3e92200fcc855307e7ee43a1cc8e7303017cf5d9a28b6c1cbe72df288c7d08f4cc495cf3719c2ebe573942002c89e97cf4e9ac8e7e5330ef2888fc87e641d785ef2073ff211aee1ca57e21a09e4114bf1e047b9c595ff4386605fbe8e2e620100c9cf814204e41187e1182dcac72143f8fe47d925e402402557df0157d957510c7e8ff2e25df0e023ef02fb217174215b007164ddd1de3804907182649c237ce4476462470da0e98326297cd4551e48b140e26805208eb5c388e2d81d8aa37d918a8f5a4a00a8aaf8a88f708c0eb8ea3a5407f58b531c2d0cd98f8c03717c666666af72c7ad5cad8f055a9f1e9e8fbdbe6391c0dd0d621ea5cb0bd185e85160a277793994940ee553c6a7a7ddd1dc8ee3477858605455141855655dada61286d554402a61be8eeb26295256a147b9182f42c670342ee560e4501fb9cda243c92129364ccba23c4c07755d5882b71dca839a52fa4e7f8a3f36ee14a7a4c5116a189b4f027955bc2aac26aafce7a379f939e7ffd8f8973beed07cb2e98d5aab09712c44a0690111be857f7822c01620c0d5dd4f7ff6cb3bfb6df43bd7bafbceee66819bf3bbe7ec761974dcb1f472432a71dd47bd9db720065ca5196ce6d971c75e561ca5eecfce719cf8c274b13b17dc981dc7b1a48ce34c98dd94b3d50386a72a6a462e15e4fb4b29a59472f2e74d9de9e85d15bc025da3d1ef9e0b7fa638025391c9a8672e9b4fa3dfbdc7689463b22374d7c516fc42210165aa34fad3efdc6989d4b2650ca051f1959023c78b68d68bdca9b04cf3a1c2a6f079cf49e00412f0bc97b4ef579ff883c3f35872a85b853f27485afd42d683af0464f5dfb34011c84aac0d42f72700f289b5d5ea254dba0c5be01b1e5ad60992160b83d0567d65cbfaca5f85dfd7f7508a2d708f0e42932c087c5fe8a01cea44faeefdf88b016a625873c50826a2b0f02109ab09006a87424c89ea62875879a2420c5f1feef33d2e620ffbd7cf267af12f15be079ff55ee8e2ffa87072bf7f51c313154b505436789738f6f8db020797f5aee7582680ff7def85e35fcff33c2b3620c865b9fefb0dc2b1f5f21b107bfb85df4bd603f8c61ea93f90f5e07fe1a8e37ecfb11ce0bdabf5b5c49f0604b9ad77d9773d288220d78a2308c06d893aaeeb471d776c3df7c51e0c47f05962074f2c289a10fb4f5421f65654e1c57fae98285be6e2bd17df37cbc60b71b6cc8548ff1347d8ebe572f97c8fcff7f8f47c3e3d3d3de07fad1ed783fff9f4f4b81e6cf5b8787a441e9ef7421ef05d2e97cbe56abd1de27abe600b7c7e10743dc9697aa202cdf8f1d7a73856dcfadf7f137469615dcf7ad75771fcdef5491609cc6a4010974b1cf9babef56068df8a48ee0afb822ed68fef8372e1a8634e5d1ff305834a3e49e9d3d937286b09e9aa8c850a5da468d9a1872090788146432a8b95257ea001149b2b929e888282aa1da4183325ed2841840e5516496038628e171652a6071768f1030e34701285d66f99f0a28608d50d072b9e5c4d57a831820c0d455e40a3c236c7cb9ca62b394ca9c200509c249184056586f040eb2a2cce540155844a8a8896838028ee00516bd2c4852b25482383192754405449820a2256b029b366892465d64841a68a151f6a45a4e8385e90b011b3064c152b3c6489320415171b5240c41215f41426d894ec7066064d5cc921c89024e01c9165054bb22c6938cd51f8a9598badb19d2159a26c4ac09143c317196458d2c4126fa41c2f559070214e9336663c90428a11414071ba53a7aa9baf58e9b49b9be74cca3dac8f8a72656bd0dd2ebafbab897065b1ce4dee7d4ecbbd7f10cbfdd8f071ded1450dfce818bd1f573f7e3f823fb27e6cfde8eade47df1ad35a6b5d27b470b02400e6f82ab042c10b7f6cd8e00bf0ade1cfaa0b7fea77cf97863fd75a6b45b9f24aa8dc770f701bf2f66383933806ffbc7d2550b79a7b7ad1ae7a949d9dbbdb9f701cc7711c67917c5216759d73da5abba25abb2013083743e13347566746b1effeb3b3419187847277877223a6f777774f4ae77b0be156323387400e7114d49c5b95519ba6badd0d0239c46ad0343737373777e50d70b0fc54cbfce9ee002063a71d390ffdca395f2de76bbebabf4c7ed9e771d92546f2785d9e3336e7e4f1ba938ae724279d88adb37e7ffd6f1582b3ae7e9c2aac5ebee8ee73ce7a67b52e64569eee5212d559797a55ce39e774329f854ab208c62ef55857cd39e79cd683b05cff19be64e0d1dde0e521bb5c17eb0e9decc297cbef44fbb2fda2de6bac943d3737cfb1fbb1fe4867d775b25a2ba59439ce7a10f3e7eed583e6f47e7a220d40a217155f395e33d83106da1727bb2e36139d0dcef0a5c48ee09d53fa9877ce379f3bf5ae7a93723e67e7eeeeee93ba57b97081820b172e5ef52a17ea55e7d24dae07ee76ef9a3ef91268f4a9db52cadb4ccd1c6366e618895df00bdee0998367149861cf3a98d9e7f9f5fc8c833c5262ee79ae31b39830f33cdb98d9f56c9fb13083cf3766ee9eeb339361c9ecc4df334b79cf5c6480e7076508f35949ec5fa0c0476ec5c50f89b55a6d6a6a6a4a898fba4e9dbedd6ab71a133e6a29a9b6d96c8d050b96dbb5daad7623c347cd84094f4d711d77728a5918b19f58ec4a29a594bef266ed3eeb025b2c9e974fcf7f1cbc64d1d8e0391609dce5600357e25662e268c3b810dd0acc9d9c6a4e351f332f9fa93a626e4fd5e969b16e52ad1beb26c5635d6d6b2c2d1bcb8605ac5dd79a497733f9fce65d9aebf091c340c6e8773136ccd4105c678aee24871a4bcb6e2dc3c24e53acbffcd19db8a58d79a57c972ed42e8c4826d81096bbb1fbfa5f17d6c97ded7e6cf4f5db75d960819ad73073f70e2f6a0e70518b83fa2b1c7b45ab06addb920805778b07454648d4987def2b81c5c396cb3d972c5c586e497223a3d94c26fb8f9aef546e6975dc9c1534ea66c054082913db546224a16e61420cd0f91fc7b75519f90266dd90603a9995a884ad9924ad544335080000c3150000200c0c8603027140284f2461f31d14800b68904074523494c9634194c3208a81188a0100108200008618641042880c0df5ef3749509953e463d13e3bd4f5de13519aa4618845ae0ff4ab51dfebe01abe3d07bfdcb751881fd87282e81b3f18db24e23ee77249b3911a8bdf9d10471db42a521bd57b61fc063326bd5822163503b9aea499fd98636b2b198025ce1fc5f43997bbf9f8d3527bcf9a49c90f564efda96311e0745d275467e542962f956a3eb63a17fa0ff2483d9d4b92a7a63ad67a2d7672403a49427686df5ed657e36ebb218483082252bd007c1282fcc65a3b179d0b9a5f0698097cf06dc214407cbc19196aa93972e772eebd7c773906770dba6326127b9cc9eb3fbd72d1c6e9af7957115186f082e6241df0f69d72013197bced5e02d90832aec93bbedcb95cfc6440e597443bb1a445dccb1cba8c0efa27e842f7cecda3787675f5ff17b00f5bc7842e94aac8d14f4f6ae4e04a814e7e1cba90cfa53f2314d07f135d2e72c09ab20b0a17260926c697f972a8bbf5d150e1eb125d3ab514f638df5ac2579a73479371b5602b9c4caa4f447c38355f069aba990994677df3a01728b3efc5a6362ca7da26b356848a2017007cb0e6b0bfc968452b84643cb0bf2648060a5562f1ed5db5dbe2a90032992ca6377ec5cfcf110b78e349932224d37b65af71df9bb4d7f84f5f32685aa0fe90951c387ea9f1632219fdeddca225283c2b1bff379327540d249956dd446270ba28da913bd64719f198504f74cc07bab1e7ca7685ebf156325b8760d90f45cd26b52079f62fee36703e9931225974b47e11061b7b5f8e4cd123475020c642c9849a4d12ddaf733a873ed89b21e4ecc4c8be176bd57767944193fb50a43ecc3378f56b6bcbfbf24145c56f1638a7ef9559dab9b0c09f8f48ee3d67e6c2de6128e417f54032b0e3773b68c96be919019aed79ad140289cefb5ae4918d29c5bd101602489868948d122dd5ede5b1fbaba12184f4768dbfdc443ead104d4b47e4a3f1fc5a57d4647da8c2ef133cc6547c13071ac778a8fe4c05d66d9e49631e502fef8a7f50b3ff881d5db1e3087799b6202b9df78c553fbe8f43f2c30707aefdd715dce0b0fe5d9cca25f67208f8de2c125d6ac2df92728275fb556ecfbd89021384e21b85a80025b99f6652dbf518b0e01b3cd91ddb203a5816a9cc868582e0c8199f605ea3c5b9fd2ffcdfe6448f51494f54671d74c69c6597877118c61f959d1ac41e420b761d23749b25eab6e840ff7860fbd760875ca99e9098107db41d065858a30079365930eb7a25f900801ec076ec769f1b8fbb5cb75d567079207502c2a8a14c4e09aa1b44b4b1250e75a0441f27a1b97f26e2e82706887f1672efa26992ba6d0f22def11c9d81d83168b79623cd773c42cc018184502c14b6fed27a866186894256fc8da80153c911d135d2c092a2f5ef335c8c9b9df291d677504ac2b118fd6664ab8d6bdd837ef4430b87437d21eb54529a1c88d979158a0e00dd5691bf2ff5a14dbcf4e3254eec2a8b1af7f8fc9a43a933e78a6f5cf13106daae6b000a628a073b69870bfe40b0a964e94f83890881e4412edb7c3e37154a291860dc3c2bf40d8ffa9e593965c031abcc9875515af44a46cda562adb793f3e4670430c19b78d5c05a4a05160ed656aa0e7b8daee4b40ea1f04318e26e1a9ab6251b8d3e89903769c90d9f3a6d4f1ae19fae43e5281e6b1e6f1e5840080dc51ab4f46e1e8bbffed5c56ac73f5727194f3e0dd729d653784e8af6d4e961112b34a0423d25f4bf7673b99c137e43ca1ab63255263a7e160674e1b422bbc29021b53160fd4535397eac3f3556e6847c369595b88b211c499077f8532b0c8c62bcbefb79652539f00a33ee44592d70a55bb4e16dd36cea3ee0902bc2034985cfd4da4a8f8150467e382b474d0d499e2ec7c6bb28bf0f93d59443782022934e1c139c37c1a457098206a3ecdb93a195305373ee8b146a5391d054b9041b771c9798218c2ed489274f4b113cdb1166aa1565e7f4682ae1612eb9f36db33fed3ed967bad3f0a0b14b870cb6dbf3a2fbf8553eb568d4bb5966a6291689a56283ddb621acd19456288248697afca9711302c17b6bef34df7b3feaf53c250b1cfb01f4462d03359b68c556cc15761bc5b527c43a5263353a1db34385bac118e282a0b80af3b563ca039100d42ae4f6b8cd7ad51b1d29c2a1d1f48c8f5a2e9c85a2b377fcc67eb569f6458d02a3056098ad86969bef8f5fc3aae42927fb0b50a52114f3a9451393072c092f7b3917d2143ff24a28e06548a5002a2eb3741db1f42e87553f3b8f9ba90161316bb9ac1606cca885230b9e560343cd57d5e50271be5b23b167128eb6db4f71f9ee616cab9f295e2341dfc10cf4387c5cc8195c387c06721b29727acd195e4757559e10a0d22ded4e3f8b87aebc202345b280ceba84b4f86b8770fc18641e76a113f98804948d0ac53bdbf74bd9826483ebcbf0f4d0c6ed5672c96332821c4903b36c07c51f339d7f44138021ddf5e6143e1cd6962a0115bd0dab5331bacb34162999866462d84aa1c66f0c7428f5a5d1e2e04c8f2170a0b11f45e6f15429ef4189a1ac2bed8a065fd1c6c634565f50241b27cf6e788604a435d3e2269384029fc780c6a505886533af823bf1a861808e66589da5d7a8814c2e57440e43ea28db66738ff42fcae1eb6ed8f71eb34676fca4c37d57684f83300d1da5a6ec824a4cb5186b86896e52f3a6230a1ecc1d8990638fef630b64c66ce2e19cfe3d3659f623e1f4847824d81c1fa7aaf5a25434cc5face81c2ed865b77c01df12cac1b28aad39b6c7b94760c72ab98821230279fadef932fbf200dcdde1acb49abdd923ae84130a5ecb1ebf65334268d12bae4541f1c0c98c9b08b8b7c824f92a4b0d196de409f7b1c27474075acebb47e0393946bf59c8c96026ec5d6ab693ea80ec96bd3ec0a05d975e0bd298280277403de136905a33d04010419400e55a2e3faa30a6c414e17d936b762261830f36429b9b083f404a0ddb9f8d8d8c53198cfca2004f313e4bbad19178871330ee696d3e25653d4fec2b7a6d7a7082a5e06a34503ef819ae801b068349c9fe09696e4f114a318af4bb51ea4c2191c8a7b0d0541583727520228ae2267d9b5a3297adc5183501a0baa08cb27c61fe0c74aa4761f5a4b72150ba34c8567e706772102194a61c12910ae0f2a7de29c28a6d8b7d211f0f40d10225c051631ebcb48dfd0aa905c1fbc4ecace0a96142cb012f20c40c8ff922dcc8d41e8b13d18c77a0c29e887661e455e5c4bc1459d47b1df0ed05a341df9eac3a4d98f59d336d0ecbdfe19efa6c789b437861a5e7d1b696c6351661246ec57b83fb13f0ecd4a0f1db0e2f774ee4b1efc5219fae5b1e89aab639ed92a8c34794082421d99a14d8ed3cf5796c0420f23468ff47e6763d1d29be940e2253e698990128c8d8e6c6b2f7e6041b84661891a78673ee56fb7adffbe2c7e815e766bff3df9878903cd1184c700dd98973d56e1760d9c895782b31c7c4b9d558e0a8d4918e7956c4397e19a468a8388221a806f9c4391325bb1e76ac128e3539e0433b7715f23aee9b9df6c037762d12e73645b91051b9704dc4c13611f590f01d39d316350d43dc8e52e750b218639c9b0f2966608892da8e2c54f9a1034d4b3834eedcaa16661e564760e551304a68f242273fbb95ee73b439b0dd5969e6848981cef5f1192026a71c10e9db1bb0d173dead9ad293ca2e7cbd8878c113d61670f8e86d9d6d26d1b39740e79777e82e9a73ae9f1bc3dd17075932669cfa4931074e6e64f8b3353613b1b0858d38d48bec504c44dad69fa6cc032fa7122c54834ce5748f2969ecdc30ea32181ce8b646fd4ac4aca1d77b8dbaf8b97ba8ee2a4c82eecbc47ba9968eace9ac3fad974267daf698b5e94389420bed2ca837f66826ad4b6dac0d228c8792ecf1eeb5a5850c34ef363886035545667f47c58132066f5299a6b7d2d43c887a0b81fca90acd388993c26129427e37c3d6879acbbc5425aaa3075e5a04e216a337ae60443a65ff4a63abff842fea0ee9efaa71008bcaa539e3832261553f432f56fe0023fa4da83d9684a0bf123661bb7622ac08b845413611d49c49ca812a3a5e171528494ddbe63acefe6e9d0de9b4d90d7907daa705366ae8eb3e4fed6edc512c00ed478e2cf7c41dc5bd5fb0531836197fc0cf7886299d2c4b23efc614856062a97726a993f9cd0ef22480a9b13c22ab38e9aa394c61cbc5cbe5687da3bdeedaa89ba31dfebe738a269342b38ad34510a70899129c811db0e1a768298450c2d15a8fee7a718e63391c85e6ce3bbd52dcbb9ab9b91a63b97c2c5e1400d5915bb266015fe228cbab742c6e2059d46fe22e646b127aa147dc277ecf312cd90cc1b4c48ec3024e0b535f5b114d63c9d48d3f61a4baec9ef67fcc1c3d4b8169da2155e08f6ad9bbdf2a6e8a68c6542c86beb4c092116e8d8261dd96b30fad68d7c139ab822e4e5ed5da611d7a4d1eab7221cbb7436dfc749b40298b2fb46174a6763826855493333a0f430437177c9193905464da6220bc8bf5e2865a64ea4192d33fd04d962565922c76f7927a1f93337e4f5f0ed67289dabf8995dd12849a1dcf05f782ffae3670289a86d3f647b7273a941397b2c829119ae32305b6e9f4cbf20ed9325fa4b6258f02d3646e1b1134060640c5a01aa14b8bca9168ce1e2ad4fa4bd30fc59e83756da0402410cb490d5c941179a2537b21c85f313fcf6031194094973a228b064b233beb4fa004b22261b1f1c68a16c7ced9e9417104e51c0d6003805443287cd7d0dfbaecc99dc0041f906d47b2d104633790296b5dc2b6728c89ece37cdcb2749e2e10ceb898079cb4aa1c0228100ee47bb3a6ac121551d4aa871cf8bd7c29422838d92e4940374960191e1c947cd77c74a2b4a21c48929a77030341f422ef201cfef7641022f36183bb8df805f73b911c1ce762ab261be7c39bd7ac40ee1208fa1862dc40576f14d5e90c28f8da69022b0e8f8ec17a43f3843081d095011f047403a64c6ce743581522b09957dbc4bd012275fa59571e0ad68c65ca2362763300546d46877060aa35030f409073bc0aebd919c118939695cf47310d3c40ba3d535b4d2634063d996acc1e1badefb2358af6432182ce97180d12fb3cdbc43a19600d52336c4c6d68bb8da2e1641ba18cefb4244b86dc56e94b11141f7028a86529f7c6ebf409aa93f781e662466c9288c8973dd8396789e7daba45479db82c28d0b4e5f73210e72cd24e4898b85838cfe860a43cea0f509d9cc53cdb72759c6b8a00b85df7d8043e43568cafa0a5f008a9728cd1fb82f71e314b1c960633441c0a9b7ce6a17156acc8e6782055b9675e8c0b1318ef0a5350c09cf2174d8312040e4b2e64a7ae0cca744683efc54574304e93cb7c6e3a58e91b8c392a7622d9a8e845d4907f54bca3f00d9cfd2c2114f828f282e247c93e316937e82021864a56332132f7d9c35bd28be6543d9137ccc189bae79d403f536f93f33ad657900d15f5bb6586d872dc47db4772066a8bec573c60af9d44fd4e827ec1542cd1317ad66e709ea3bf0a39be5a41b05460d92ddad83c5e6c46f656376c5d9da46d81896d989db8fe999d882fccbf712ad35db60d27769af4740b84b617d8ae56c5afbcafe9186f5fa5a489f4540d85203e9946247488244439788504be81af5a943b23b2e36f8447efe3455fa859eabd13c58fe1ce50989f68da93f5e44bfc4fa5ee819f3ab392f1cbc1f19962ecdbb899c2419783f2425301c24662c60b6d2cd96f7a827023176ee5a00ecd334b251551ec42d7952c833b69a4719cfbcd1157c7d850516f54018fdc47f97cbb6187105cfde69415b07925a7d3fda26903b966f26762551e03596f269569f919ff2fc18b71468a0d0f89cd2537bd74018793db2d8cec9e22856a735f3131a10c5855665929f62b8192426f8b3986478ef20c8f880da3509a29ffd4b6d5318142870df78fbdd959f557b27190b8534833287427ac703863503111de158081cf096d23c43e38938c14bd4f4733fe34a7394ae6616bcaa8f5e2e24090de8c8a1fc42a9296ebaf0b283c3993382cfdc19d9f820c88f429cf3e3644966349e787c3a9910201718b7dd053f5a23906e3c6adbd65aa69885861dc1d256f79b94000bb243ee1ad219e228b397d47525ad4200a6d92c3e7cc20f1ad8e1c2c712d14245e5efb8a40b589918c1be3f92f80a70b2125e19f4afcbf6fb4b64959e59c48d491907a79ce0a1fdfe03a847d99d6b85d5ba95ddb34a7b7a05c03cd28422aefa7794c0d4c9945a1cb83ec79ecbf92abfcf0593de5411a2fa1402047a8cbdf1cdfc400ea12321e723473fcd98b8aa15d38718078b4d76f221bbcbb1eae47453870a4af2050811caa04646469a98c82fc289acc0ce3127b002d23bfc1e77edfd59a822a6fe48331beb7bcde01af4885b1d35736b5bb1531eb8197220fae902d0c289764950725d87faf9f35329174e735af8cc68a8e550ee57f22ea350d1b254ec42fc7628a715954005d280e4a69224fb3512988dbb66b5a0bdc9fb86c5adf8ff5368dbe34c2376f136a0d86f1265295d85ed193834998644904b9be23edf5deae6444dc6d1a1a8214d2857b719592cc3ca2587d54c1657802210876b5e64cd3a1bd2365c96446ca2cea29faa867d106a8d520e6c9fa42ce63ba704d15b07534291874bbfa9ced69490987cbc6503f447df7add8658ac860c038f0f9d480015a9255b0b3a687fa5cdb0a1422e678b0a8da161f1b2c5bc7113f342d4263ed91011815aba8d8ac7c5a50f545bfdf6011b0c5a0e70eff0cb0c97d20a9d66b033404623dc114ad7f2be92f2906db55c6c2607b0a1527e912c57025a6e2aec23b7fc14998d01373829bb2a7b7e0336193b9201f7482d3c411776b38af59b55ac8b55f901044c3061409391e6b45cc2138a6f2df1d24f1938ccc8f478b0bed1520b8894bddc801a25450050555122f70b2a0d33eb31a17e6545738d68ddd1decbd00b6409a5156044dd91a69c1a6514bfe587f634cd80ad714a1d1b6c342cfeb85e6675b8c28632caa49ca2e9d330b3474ab0e7dcdbad948bb711456bfcafa804cf3bd687407de94c997507745d4771824504329d348c490fabd602b0e60620944143447559d21630d4e4dd28d4f017a29cb8f368d0a0bb1780eed97ec81f4cf67f18b6e4b1cd403a8f3697f530587f3cc1a755f5ffbbcbb279fc539128bfcfc0be79c440aa9a5b15b28b283853fbb8b66b722aa8f135687c281344deed363230aba3129549a61ce006623b43cf0293772146b68c7ccdeeeaae796f5ba21cb37b6dbac7d1863261fb8df9dd3974ac4107e847a0eae935ccec68fe0a5b8004dca57a3de7be07b025a225eca3f32e1017295a29ee17220178fe033c140700db6aa47e30bab23ab0ad57bb4e9c418891dc33d2c21aaa9330057910c37ba7bda47f5eb97d8cfd31ef03eba159f4eafbe67678780b8345218bf8237dce1e20dd71be1a78cc6ee63c427fa714b514b90d1250ce0b9d52d625c01347b19df07df95d7c2384712cf220ff6b11a4659c767985c09d5d08b198f5c6156a1e205657bcbdcad0c0b5b888df51e6584b779d3ff87be2dae76ebe8008abcc48b7f1ccf599587043bf47676b0aaa55bccad8666e0ae2c543ab8b94aef55302ac911884fa947f7c3f5cb0a4995dc47fd23ed7b36aac4a079b0f12553605c06a157b54e2d0b6023e05cb11d55e27ac8b97fe7df8c5574290ea4b5cbfa189ad1e791784cd08f0849431ca1e6be655ec8b633cc4181deb6a02298c4decaa749024ea63ca1fbaf8f5489187a36f61b9cad528fb8d88fe1d5221c8a5f26f03b9f502e5d9612284859ae11a0ca4c42475e5964765569ef287f02ad2fab7b00babe6c8a413d5ed3eae31ad2c79f51defa8655f4a79220f0fa2705b9d444ef90dec6c6c2e6a8d72ef052c9f210f690713bb70c02dc78a181fa113887841980d164420f2f728487c917d002aaf5b8c14a949b4a5ff57a4ed686c238479b4c3c37a6389e0183d8b16a40a1f488c4ea57c39798f2a12b0b821597816a20192805d98823145c003d1ca050df521144af23c33c50fb7f5018c7e59fd037408cfb2f06ce74b8586561d0ef21cc0bbd8ef7043b7f049a86cea334c694cc7ce27adaf28d3294ac8710ac7c9caf83c8378fb4c4682c41a46dfe92b82f311852c45186ba56c1a00067c84ea80c78484718deeec548b48d81b1afe8c20650320833428f53e51590c8a9cf9e2533617eca15a187566208324b324f8369cf7fe8c2acaec0be90877bcac99a3fbe2e03395be71c2e021fe750f1687214bdd30fb2492d16d2539af204b889d52935668a78970a721c4be46b0c5d167d7145dc867d878d7752ecd4bc42be8f9d303f78766b8a3da00134103996bc58a36093a5833d5e36c22168b72cf2671b9162535fd829fdc10ebc27ede344aaca1e93cbb4e319fa5e2679e8eda35b49160a0905215b0dbde4a226d27d4a70a5929d00dde5eed82c40df2a563a174881487c45c5842cad2c34fd85dbd654f391caeb4f958bedc792861450941880507ac329a9d8d9b0ca0e171207999d810667e5e739cc4c08898e313d19615a931b0654c01f1fca88d1a90fd9b50d048215b51cb2fe7781fae99000ed2a93576a9259eca465f61340010681f8c90d063b62154e9f1f63aacdfc07544a82f294f1751cd058ab553b775bf53cc3f79457bb4d66fe722fab803ba8a79816f0b30374d0927c97900e008e37b99d6947f0336d265f1a7704f6ec755cbd99d40d907f92e254b63b9e392d112301ec769e6d0bc1d25ed992e6c7c406c5417a36316cc93a9312939f700687e79a3193b6562f24414e7d990d3888547b0d2d1044adc0de82539bc21cd46d7a9b6eeabdb397771e23abb71310738a7982a79fa4a47537863fcd857e2966113a5d6142b76181592731252c9c5e0305acfdb269618865ffb0846b591ebb79b687da1a8a672a111295a50957871d675e200cdac536c1179ddc4ddf3483e42aac881fd19c82e2018fc75b3c350c39a240e92c6dd5cc86980b3141dc139c7c07fba59cbd7ae33a1332cd03473371148abd90bc394cee8def9e7bce9e44a49004aad499328cb489e4b8797cbe6982e4755d215a113d301e474af00e4bd62deb52892f66e882843ce05961c97052ae09df3f01365d8c669678090213db740bc77372ee79b962aae5d4cc7cf6b04f4bd79db47b166becf02c0bb800063dde5a7ccc03a36d459b30f27366c9bfe013b8471a6a31c8c82a408288b468236afad88a17b4d02262e5ccdaa6c2928b235949d1b82da9be61bf6fa06ad37ff75adc5ce46e94da699c4d73267a0636ba1623256dbb9172d6133f9bf7a0e25a61dfb10d07f1b212e3d0e7ae191455ce8bf2e62d050203ce815a1f08dc741c7b94b5ea6224e0122a659f591d4104feb32a5774c00e5318dcd14a007c62242be4036959b2928849d4cab3aa374b142be4a564d91227ecb48747085b48ff2a75e7a99a0c1250ea07bdc0213574a73244487953bdd9e3572f6d25b7eea5708560d489e0682a1c6af605925381de0523fae2123c56fd5503c626e4ba726824acbb473ecb2a0aade012eb26dee2014b55e38f0326ba0c2fd1e007cb45ffc9862e071ac82c5cc0717d6d61516b2ae9b3bbbd6565b9e753a8a75a7cfac1f1ba1194650844acca39fc79513d1a906d96503f6a59e59f2f723ee070d1a766a81fbdb8accdf008723fe734b4b88df18758583e146bfec34d5a4d017ff7ea95b6f39c120f2c5226c1780231b93883c462a0ff7f05fe59017fc0c8153627f3509c1bb55e56417d4dadd3cc3a354f95e9ba285ac40a9cf7070fc9f93a8d9669af8d4709dbce18c49fd2260b725bf88094beea61da73d741f7e457ddce5be40885d261841234dce488102b47c17f5025f0ec763305f6b2a261cc80bba9d2a8e033e673e756d81f6893727349e235c1f847f962148eb8f73bfb5aecbd5bd879641e07984285513bc5e773cf1a9c3117e8f229c5fcb84e749e7b0856b32aa349c46fe995619f9b05d6aa74ee9c8e6c470a381c1f92afd9f4d6e333ab7e332ecd857afdf7f874fa769f199adb26ca590abc8f307e91675ac80e0f6c4890f5b28476ac875e4dcbbc3b58765f8cd0623a2250d3208a42a4745ce857672c970020c5e8eda6aabde2163231007910539efd72922eeb950c7e8d3ed10d228a6cc6e7f1585e7abe07a9099a355a433922a035030b17521b37a94f225891ac47660b2b538e856b0d0ccd8a02f2e32796aab61676e26db55d38d7d0ab6e9066022abbaa8e216337647616778ec8f18a1204b8aeab7841006542b3bffb4fe611bd27dd733a6601ae2f4e8b21598dd9dd6d822c928917e719f9a124c9cc9d65fe9fcb48f951809991549181779a6f6b614608ef1cdb00c842ea257bc0e1af3b266f8da38e7b76489ccb5c6dbb3de59432d31cf53ef48e929a7bf612cb5d7b6dda6e5fa2c04a04f11bc2dbc4d47175d6a5be56bcb5cc036d910bc8593c95665e286bc8d061a3a33bea5f7e996e91eca5fc42e85306448446f6a6913ed9279d456fac0034236feee68c3bd051a91cc527931450fff6bf8a8f9e0fdc290cf2c653214ebef56408908de8fe9590ff2bd83c4f9109709abd23f4be0c31a08c63bff9e59a420038ad1f3a45eb158e9b7c2c4509a60a50aa37527dfc69b1b7c11a6bffa4d19ab51df0c904137420c5c74a2c1a4d39f8802e7706549110afbf95520460bf736a6c0d8bd588f48d531ed0fa15acfb9a809ce45f6a648d275648fd6c94e78490d997a660e7c96fe1b23dcd907245ec3b2b551615c9983c279f558fb4a44ab7ff362770a4dff2916127d63d1162dc5e302647ff249943cd2f9fcd3276ee2f74831768a38f58532eece64b7b0618417d7ed25a2c0c16df27735a287ccdb5f020478c07405f0a3425fbcd0ab4f74c630111205834f462ab6be4b0d03c9883205165cb288bf579f79cc6265c3cc567b32fb06bd03a493b0e4710e4060a0fd13298fdaf1a248af704784017872d6bb7734208123b5aaf928b60a7f4fc5f490d9e167022d006ef42f240642da0b2fa4dd77f9d93151ff89a743b104d53d5788302810885747790f6c4a65b8767e24d58e8df1cfac8ea2c14547bfd03d88fdc12e307717d7866b45e1254b92706f03bf190eca92623a97e7ca5d22faefc74db1dd34529e636e48faecb0d2063202408e310479636ac246665cd678ba69d82c1284eb68671a14c80824a8c22b086174d36293b8592e3c8b1d881aa914c5b645269cb5a3786599b92a0c44f2f8e0fdbaf345488d204eae4c11188dd478c9a8c74e4db6a3a4fb6908830e1cd4e92d06f3e5eb8c43c9b726dce8e05091c6a078389941c1502a4cc2c0d5198792a67874c5104afcdb0c35aab6e4a38dcbde4121af9daa682a110c2a0c4ff79c7c8be036a67ad46e6bdf7c815dddedcb837103614e771fd02c76e6f023c0231ac40748b33ac12b0599d6d3e0220d69d12f66c883188872c97835502ef1b3a5304b2c11ec39a3730199bb347cab273e5333c942b39071d077c4b5ed2ca0ad80dd3f4c4bc696f30a4b3a878cc9d28806fab876c1f9c4a09931222b141f29889a48a5668a09338072e648fd94d89b9fd64f14c749d4203831f1acaf36081a4685dca0a9f724139818648dd8b0047b6518010610718b9e02f4ba245f6c27c1495608e122d62a4028daeaa6b61a152cdf385d9fcabcd942eaa1a2e65c005196ccaec1bc5de11d02012fac3489397e5ccfa0130366cc8e3ceb14e16b431ad5f09a1e03b3d4609116b1f2bd805fe150b92eb520b7c61c42eb42231a5b0eca1e63f3de3ce9f5d90ed19b1cbe3d79b7e2619458246f05224f90d5e55ec834a29ad559eefa90c4df9f2ee5652ec3a2ef9e5ee6689ecf417e0d8b90bff798eacfbce838d6821192675e982f37509437bd47644e3d0356e90949d65eff1dbea872bde1feeb95eed84a99f2b534b42e7073ac19f53a43fd18f2e1e1495856de6d8cd816c771c3772477f768710d55817405a015bd0b20b48238a4167e2072a31624f8c2d5dbc2c742b1a84136b70a600f77c8635c2f7b3594bd5ea82ce12fa1dc4d51a4fcf2d57a4dda06f46554a07d43cc302752e21f708812b8d64fbaa732915b2cee58531c45882d8850c83fe3d6454a6f43051b9914c65427ab843396051fabbb8e5fac9806840565190108b70d616d2b34052ef8a6a08738c389d27a041d9e2fb8ae9d049903c8fdd79788749988e606b145369b4381155b3629a27805d0cb5d6e88c92051425778162bd8990c2760b2d43ff6edf68f150c24b7bfd8bfb8a081a8a4c86539941d3dfc9d0d686fb6340a1e0bf4e91f250abc6f10d6c8e96e2db0bf42198778216965f1714da753299e3c805856866a3dd4d5c3c78a06c25006fb5aed0c68e6eab4d5f8b34c4844e7c05791a7e9b48d2f5d7592c253d07c1b71d54738c8bf3b9dc14e9c4d1b4e2781404a23989bfeeb8f86b24e3af456369b355bbb4d1711162e0de4588b085679c8811c1520607070487fe7015a03f41fb9b1a9cdf1204ce0291cbc590fbe3b253b46e80091a41a7396dd72c3b2b90df029247d004590610f69d504184c387a35127ecbfb62cb619d103a831dbfb29d8a1f05a564df15c67e2c1800619d5b9b0e83e5e94f54139e0b09d041e9d051d5b471f286adc43e9c519b0d8a560472e6ae0f1fa4739db593bfeda436616a4d7837a2e3e4503ea7737cc1b8fc2272e7cc01bc0082b8bdefe370b2d8936f1f2219661c6bd1e3cb9eded69a91955025fac90a044193e2236aeb9f57e1ea5b9496e410f1112de890a990c2a369c7a994e02f364bd7b9b822b249c1b4ba70aa118b3b29fbc6fc13580b332fa7d5028eb12bbd9b399881ac64f3aa40fa43114dc85ddec807268978fcabd16f47220bdd23c05781e9a9678867e89adc565931431fa1b8c9e4970f44e06c65cd9b655731bbae9c51487ea93de40dc90b0affc89cb4474274984fdadb2b5132cdd54355b0338843f9dddb43228a64999074d799e12a49ce56b120930f059f24a97e84ba82ee018754c2212bacabdb24af19ae34ac4180a202b38b69cbd7b4d5b1594c32090c54a5691427b880b691139f4c728d0736073defe0c6fbbe33523499f1077803184611b02ea8cf84521bb48a1e0185544139163f059536001476716c4bebc008a81135b5da3dadd39ee40e8e447b1802b11fa87dbb2d6565d507c5341bc4287d748b2fec15af76c8903eaf688a2dfb718277d343bffff6a46741ed0a6078a1393ed9ddc9af33d77d92ea7143b492c7ec320cd390b65a2c139be57851666bb0ec9d511135d47161cbc6a24473f659a7d5b40a455ddf98edf04c6c021b8c25128121861b4c7327f395af80db0d2818cd8c8000ab946060657dbd45f741286658d2be160fe7a21b831db389f7242ed955bd38e4e57aa646d2f18d0cfb16873a212912c940a77b2d9ce6aec460d26c4ef3939a4b4da4e2e420ec553c93b11ebf8f1623af25ca26b6aeeaf789191c5d8b5ef3fbaef3b7f7399e09fc0e1b8dfb7846d313142199e95109b4979ac1f6e909f79e721504f11e4c05f27b7c7b6abd68eecaeb39cdb4fc4003351cbea3604c608792bdebe55f5a19404cd93837228524d04c4dd3afae7e1956b9421eba8ec85e1854a7069f8de92c9fc67586cf01d2b864dc4eda66a334537a19845282e2510158af8e4fc5e327c73a65a721adce365fdea1e2e85c19bd9256c30a07f15fa887bf49524083c28d43d109de175350285e3435c3a3474991104b5074466582771f57e428c24fb054cc8d50b3eec108ef0efd0e78691b2e3fbf99e7de5f2303dd19c1dcd81853fcfc51aa53396a48d7a2646333345fba3a1b11f60ce94cffbe3dc9887c20701f9bba309cbc9a1631b915a99eb7168eaeffa96e6a901ff7236c2914a16a4974768eac2492535c58d9edd0250a657d5de4afe3d9dcdbc95b9b1c897b60a73274ea8bbe4998739daea9a67d7a55e2eb21b904f352c6978a89d574724c4976fcdb3c13ae6b1a7f9e4338f466d09c27f39f1779bb9141c4c9d58eb21535be99ac10965a17405ad9f2c8f4c98c9890ab1750e86637abf293dcd732faca105adb0c1218712c7846e2ca19bf103b61a339493b81c1313d645f232fc41fd5b1e74afe82cacf9326191c4c8e45c7e5263cc77081ccc4f1ffbc54022eb799b8f940375f572963dcc6c33495587a5221c334915836dbe14aadcb446457014bcf2c21ae6f51a0a6b48685532bf4e23b9ce67c635b8f9aa0b3424362947f871af1166006d03450ac2bdd9f5d5f651237e73359909ef9154b72c22e6ebcf1cf8be8a9acd8a85f28ddd8ed877836f8bc403a04bb01fc6b684516ff96c034657cab64ebc1f356524439a24270a0931237331ae3787994bc5788f33438cc5d2cd7c2f74df911aa4bdcb66f594f8295e994d7afc8a265eef36fd86158cca1102028121454dd3343f6ca79a26e1e2f1a284a9093dc810d28592ae24487876a31d86f7719e1c764da8276d0462e6a501f0d7a97ba73f5e843620af0708db8cfa946ccadf3a75bb27fed7f079e978163cea191a6d7c2b43470454d77d72fb6ca2b95786441a0218e19ff9950f04989700328ad064d6d87123ea27104cba9010a5aece7ea260efdf08b81922227bdd4ec4aaaba79390048c0936c4555fbf88d8e9ae720a9562225d43e4f5f51671f0af2410ff00a170fc5aa192ce55ec56625b6970f6d036ca61e0e05a992efe627536f1d5b0f88b8a2cdc8125244eeef3130757bce2d243222f22f812dd4f3c8cb274a9507f0f91fc92c95496e22f956a5459f84a29b43a5a42a9c5e85a1d47f3a368fde2b4c54337d1d456ff29f305afbe5fcd0f07794b3b4b1da4b5dd83b237c058d40806cb3e7be74e7255aafce2d3c2e8dbda6667557f54520a437d53b57bcf43a3be9d3aa6f3e77984a002cea5d08319f67c21a957ad19b362239457934012566224be8a1861e244ba0d85598d80930713669b2724b6bc75806f60d1954b33de1443932dea0cbb68905d72cd12a2258ce629679970cb8a45ad8cc880679ad89a524afd4dd4d76c6ac7911c9eae84a7897e220e278faf1d59c5360cfc769a3b2d4b81446356d53fc0fc7fa740218a158dd6e3c6f0e52617fd114fad609708d9f55d1a4cc1f23eda8e4e7618fa0186bbd2339c32c05c9fb267183aa6d666d60b5bf3140cc5df84a4aae9850eac6f76164b253374ceb50f96f5b13a965b9314435bb16330c403b4a550b38f5512b3cb8b8da868ab13c20af244a213a2c66ed7b68ec8d394b17edbf048c0306724a06482be52bf58002b8405881a164020063b273b48cb62d0903c29991710ec99306c86446b72d480f7a8db418ce79b5fc8dc51c06ebb54d032aabf2fbb41c4143d294cb87165fac11153f4a234e1f2cad407a326d1416582db6bd31f8c9a420fb52997ae4c3f316a820e14a65cbe36fda4a8097aa94d717b6df2936813e8a598e6eab5a94fa24cd189d2b632d30ccb785f4e3005da1b8a98a097d214975741fb9942e489b595681f7483c8a497f873fde3c5bee3d07a1a9df318ff3b7fcd5d5bdd5ac88b8d6ad4aa5cb4974ece4b618e3aab22ea659a6e2d6d2ba7f5e50c860dc0285bd872e56a632c5b08fab717af042c19888c50fa8f9078e017461013bf098d13888450f21f43f608228c40ac7ca4ab465c11af09e4f04086a0f4af214d16328c480c7e104608031242693f80cc48e2308298f84d60c045248492ff18b26b0f610462e53501671bc91095fa07a4a601842288890702bb094c4228ed371a2b082bf23461fd792404a5fd02c2a800420862c403010c7fc8104bff17122c3f0ef1fe7ca8b13f0ca1c11e878c61af78227824243d3f19a1e4ff42041a7043f679c8364902b24dccbc5a0577ed0ecfa049aed93a34ac7ba61d93ea4e066e69cc9a49b9f2bd2bf4ea3c3aa9fc3c1099ce7370ad02576d9b51ebde4ff31cc6b0f340a079ccdf98ddd51a573f399b96bc5370eb7effe9ce3d1091b597e437290fb1c67f81c05d0dde893f65ee2ac1f1b081b71c493665565bfce1f3839d86a64091d72dd1e6577022651de0dc00aacba2deb77fd8e872596431c6df254bf8135b45a3b1824fdf0aa9bd6bb35188e2ee66760e168523682450ff6cd5631f76c6cf9fb7a5f550b95040154aed94ac4e4a1c92ca8479b00b568a843df41a2cf7e820a3313b9a88e11032ad8f5e9c2d4d72a76b877865ca058fe88650a78790c9fe7422e63a5957d20baf863d7ad3d8c8d98ecdd218874fc62f65168d801d419174e42c196eaa881f982ccf02ed2bdc1e88e9ad10b5b3e9567e88f5188def37b7e6b7370ff9fd1cd1d959eeadf92b8ed4168774dadb6be25c5d1a8309cbc6d517d3b61bafd3f2d1cc5df53319d8da5fe8c5535e1df0fbc5d44c6408b3cf0c21b5db3e06089ab1db365b1034cb48e8d7f1f45835e056a7cdf189364c8082a023cc57c94ddb1fff5503bcf5896a930978a529501eaad784330a906554a42767a1947e28536bbbf6175db060d315ea87487f41587bfa0a3977a996fe89becd8e3249b3d12d8eb572f2b6933b984307398ae84debb4179187574a2ff6b0eae61395fcd2227acdcb44b08a541d5f094093f8aa989ad187a10160b3f98f3a072635a9ca97de8cb553669958c861f127255af6233c887e8687061a1dfe9de0d66fdc5cbf8ad5fcc2235adb0d81b4852e58063523637040926e46ba98fae5e6ff4faac6b76968bdd5d83c133221a076235aa82ae0f882f8ebc6441c778a4120fab6ec7262c1a09b537238b7edf17b6f4654b2541423acfa90e82a1328082514cdb1c98ed130fcd48a320d61ee4ae7371cf8453f0df83c0970e44b5a1e499a9e37fa625701cb8c70235ec98ed68bdb9149ba0fc4a647f019eb88e34226a2cd2cad279499d626ca2a6f2b035f7c391281fe67a8b933a19ea1b65af6a180d96e808359098349c1e4c327c96b3a5453f6c3120b45820c480f48c31fa61caa2a324d53cc54b295c373d596932f987c2aec77ae20ed349732fe7c44305acf1de18f83dc7614bbbb773cb7a1c8fc03a26847a77d1af1288ce5e40c67aa30f46029d12ad467b4243837373b5d3773d0d61ffcea4d36f3d8a8b5b121630abd2902ee9f8d547fbe3dee7e9490b9cb674c2d58bbc05f18e1d2d95ec3430ebce3250d5f29fdff2caad786b5f9630cd42a8cb86479784fb444972d21fb8406e3e9efc71f239658a3e984d8536da6084ce1f9166b396a9023ad73cdd74b35f735c97dc8ca1422d22a02d02328647fc888af30902cc314ff726fbefe56c41904b8df55d62cc9ec0386b7832704dc43c5065546940aa625fbcbc74c262ddf426d2cd290e408e081c92bce0b21814e3ee9e0272bb63c561bcf285e6d5ad115ea0f6c792791f028f742870beef24a016df4c7dd2329d311c9fd2518d257d1d19a65f2277a4109c4732dbccf7a2937d4e59266a4901e4cc18b2b41ab5d543e4e3d4fb04f0b1eb2b463af887badb13f9362aa5e8242633e22e613a2e58550edcef0c564fcc5b129603549d5f2c7795104aacf47467f2feaa9b95c6b74b1d9a23abd47a233e47fda0308fa97aa9ec7cd6017de39841c393fa5a5d638d15263495de4dc5f4c1d3f21757af8d74e7e38e0dc1f8afcc563151a213da8006a422ca98104bd12f84797bc24d27e8cafc5aebc7c41e30173a2b8790a27612fb346c1612b91348fc104830ad31fa9c2352538e54a24ca4547725d595e19f45f57351089b4a55799b58a22d30f2565fe6290438d2cc3dbada54d192f4e3878308bf42778e0dd7ef8e6fee87a92dcce532e3ebcb5819c03adb8d5cbad7797638b7f180ea3896e61b57831aa748b99dfec9e5bcece32aa3b4a1e5389807ef78fcde305fcf9293370b8fb5bf9ea56442ac0e4ac5ae4dd810081f42b93df84181d008fbb626fb18132ee2df0d2095776dfeae844220ca5dd9d31e198de137dab97c0ea9483661345612561f0d2785fd24c7a15412fb2aad63f2e86a92f908858ed608f683009e0f2a1738b654eebcc9c21ed4acbc3360cad320e4f31d1f79ef9a6f8d7405702ab16bef6fd7a5447a40872f8855a7fd98e2cbeb11d2b7fd2e65690519acd116b9badf19dcc0b985529054e357c12baa598141eaf48ba6dd36209b92a095bb99a0fdb004ba71a0b338223d4f8659b3315aae11f222f9ae06a0bb98495a6793c8f4d2d7a1b9ae1fccb7b98d2e00bd7046597d349f97ea646968432799ad8404d439096f53aa013d5c5113190301a827a8fb7e42263fcc4a1e2ba22266f7054d33601296114135c6638d24a3c73aaa10712c87ac1997859ef9ab4a2269676f1895cd68a6e41a1c47e37f6b0f09820eb0a4bfb918504af337a9083fe5dee52ddcbeadfcb192d906e5a8b90ee06f6129f7d41bcae33742ac241109b19a142707ef4a97f276c9b76377c7025389d3aa81e3f35b1545228a41b8295cc6ae474e327fc19113d79b2655dd06926411f222c619642ad351fcb467b4c12946cae1947dc8ff0072ebf7169cbeb259ddb00408d87efcd62cf6912217fd7e7dff1aa67627416ed5378d05ca8453ea2500c3666b69b84dcbe80ff29c0d361fc363b4c44f08c00ed7a0b68aefb7a98ce1b1a54c01024a6018e2e6d6fc64335e0b1062d364793f18d7b540405786705047cd652a53a067210b74f5f57b4f69b0a7051ae667b7666c2c0decd2249dbf377e91d92deae87881dfc625ac096f0dc1133eb09911c420e30d034bdb558abdaf04f04aca0b1baa6b2efb569aefcadcfb794a098d6470d82cfddfdcd8a41ced4a8d0b46b0ea1da5d18b7398765e6789630969aa6f2202c1058ccd9cbb100a02bf07c7e5e13b6ce7de8290994cd17d6aa1e5dee3cab48e054317545f3afc853887e7dd3fba01537543ee635b514da4ed51165c14eec6989d22b13c7dcb8b0f7b21d53b7a9d0096ff3810670222085d047ad868450c5d6ca72e25a4ba6e241f08a9bb8ad45381e346e870cee7ac46765d81ac8bb17e4eab044328055bae0c73c7e2f042d9858d5f6ac8f720e424e53d1c7e90a28c9755054551168804dedd34f3d1ca498649d1c850741cfd899e5ca44f8c6cc1395f8e20be7be20d9ca2f178a07a5c701bc964737220967f7cf515ba6d9926953fdc6e9888dff2a3e0f836351926619e73c90875a7b20cfd17145b6d6a8e4febeab53c3945e0828f2677343f9d0365b607b7939acb5f195879f4d423df22aadf0f92223c3d52d8cc7c67dc24812049ed70f8d3609e4e3091760d0388c685ef6eb55e3cc36dc5683ef2982d7ab718031d380528ba0b1e44158630ea60b1f435640daa9d851dbf4fe725ec774e5fe617b0b50be8c65e9dcad61d30a6cd6fa18408c3d91c278004c6d760031f616c5e25f46c763c09b08bb90d94268ce7918d5f80349bdf413289460a448707b7d612e12b3e6602f33e835d3cade16f6fbd5952c1229d4d6cf3b16e39b56c5971e30eaacca65a500b22e0d21b6e1c2ff81d30ad9d28dfb84f063fcd333d33367c25855f97cc942d76de0fa1d2c0ad6b79146f4c0da723aba0f4d40438254156f2cc2bc2871a088995e20acada784f07fb273b0d9036a8d3cfaaecb52570b232b11e3906514a825efefc6a0252475cc29b20146d6fcc80045f2001373a9ebca8e08d3431a60a8aa9997ef75300dbb612cb28e9b00163168a0fc61f14e1e013ba2e2b48b3a2d1d6241ba42e74e1d39be268cc00897e452c6c0ba2dced96fed80c816fcf7cd4e21744104063b262ec586d272381cab1d2d74d8b25845307997da81da0df760f896b5b931188ad3423185c912922e8160a58abcd2ff141faa8f94717fdc8e672228cef1ebbff40c76bbbc22107d59068cedea40b49d7a584f1b05bd5ef9b9e5b869f3ece375043b846df736cd67a9b0eae179a70eb20c523874a25d85875d8b9290ff9204d34c70b56ee4c7304207d1b792a0748188b2e52e0293636e88cdf1c24566f592036a0c677bd559022c24cdf344326e367e74c87fb7a840e568accf22dafcddf358c3c5301e1dfbd8769bcf6bbb30dda69a559826a06f0977d0ae8006caccd5ba16d29989e0b6047edb922f598f59b73d180f5a2b6bb7158b9d7f338b2eaa5a9ffa890d27fd8b22cca5a1053ead8ecef333ecc6b165da3921f4641bbbc5ba3fc705f5134b264471d643ac6413b2bfa9e965170e6392bb039928344478f9bdec14a5ccd9c44938f1c48ee6483a10e07b6bbb3e75201f1b189450445a9d72e38667b679fea4b264cf84c95f29602f51c43c78382764835cfad5a109aca3fe971eacd7cb279b3f28656c5bf41812e4e612726aa32c710dff5fc28bf29c10e61934915e7ebafa42bb6702f6de8daca2b86caebf8480c4846892c067b26e821aca4d14e712222ed1ef894672edd86831a8f9bc9426b333dfb747b955556bea70e8b94e29e3e33c7d0251094558a1143a419df040cde0789704a1a8bae296f1552a7481ca364687e1b3b8edc7625e3fd18b58a26091647a6d5a2cb0a0b0c058a65712f071d236f04619f731a644a8c14c73a102d210cc1c5ce9287b9d42eaf99a4f50fb1ce57a49d963730cf03d4a86a8eb96b2cba68255c4ee37941dfbb03722057795a864f8af70f385ede3eb2513b237fd32cc0ad0304baf82596186d96a61d0a19f63772b44d90cb6e67efbe8f24e6867c942572d4b18aaa1361407b15ed08f6b0e95c30c6d79c4a4a27623715fb183c8621e75c40c037454d8b1ae62468dbdf5a6022d5a872cfbf20961d914f8490f1fa3cca8115c0302ac5633dad4561e34e5f82fa3ccf06ac04ed0216ae08cea7c8a2422639e3d2350b0990545618cee51f7f8ab05c3323bfec5f2035099f1371271c46d43c883d0bba3ec2edcfa379a15048ef3ce71dacd1de776ea69e8d3cf4bc609710b3da6f66f45e77a66170faea5ebb195a0392600cc01730856a61d15aa5f1f374ed00c886a91494071b10b149db0f4bfaacc9695cb4d917344645de20bb8ebc3346ac0a416beaeebae532516467577db430a8dda078cdee3a360a289d98de5ee4261f2b8d7f7d20b4e343a97b14dcfe79d7452aaf87fe9b8a0d23eb87393af336598ddfed2653b3ad83b5d4e9e0d87b1edb2b2757b6b5b07aa0c726d574be22e024f0f402bf5f95d7e6bc9d32c25c842d41130e6590e8899bc282eeb0470438209415f7ae5c45db8ca3ec3813e5b506930efba78031cfdb8e0adc2e36abbda80f9a31cfe5e177b04801d9117d62d03090b4422d7e51a60a04b32a706f91689d7fb207cf33a4708c8cad3117936caa295ff3334683085c2dd8dfbc1b0546dc9418eb8f5f655b63339f8c80f948c1fdb3d25e8f42da796a4adcfc86fc9d5435a2c2025c127fff0990062c4fd4cfd1acda5c01e43c4d1585d204e7d0ca918e052d0a6f6d5317efe194ce5fd342fc75d36c7c88f7ae553f4ea7819b7946f527b93a66472288889f23979338352a803c7f5b9ba9f6c23093879ce2f011ea8712d6b3f2b5f645184df36817e8f633324b01a81535344cd183bc35d93e914044080ed3ddcd047bc8a5c47274a52051e6379153841d729620fe5041fec5a59823882ee14bedac42a896885ba5c9159f1b6034b50a2f8ed10deb8b9040ef0040022bb2c4c3c0f9d8106a5691b59d7b29011fc8645a0479ade02e6f6cdac2ad44ec37ef0b6a364a84b1d73947fee2300e393fc0b9d72ec80cac20a4f9b1f10f92c3a33ce3d09a45a5f1c823cd22967b6c3e38c28894db7affa5b80d6b804abe2ae0d71ac0083ffcc2d5798983b38c151489407d28ece43862c696cd3f677b3f6061459296d3254985395540727d386b66ffa2a971f1defdeefb148c838b0e3d0046456d80e0b8730ef30f6164f214acba364e7b0f6fdb225a77f52cd4159df2a0ee82c7fe0aa8082811f44bc3faf3af7c9352568020210bb20461572f065c5dc037cfbf81a34eadec2d4c74628e578ce074bcc80665e6bce73d7234f7f31e99d9c9f31e33fc489f3e69938592d4517b2a0dd977af3d76df4d04e77ede41f42ba5d4f61701eb879c4167495baa47ba98d1d81c56994cbeed70f27eed3a2ebcf4bf531b06c46d5947db932ba700d202c6b278cb6f98ac051875c668a0f4746b6f3fc0b8605d771b98b181b2954bdb6e4627f2dd3dd5060ea41cb3769e9f3a1476a3006b263fcf9f73dcc23dcda03731f5ab1837ad8994b1fb14b8794f67d2d0d055cd103faf25a9054201adaa40d9a04e04104e9d747840891ef84fba5ad1f792026d526d401b2fc0814180b2709d35f5e117c8b45a78eea02ad929b8111bfaaa7080c5189b3c7cefd6e0533ac7fdaecb7df4d768bdc448456f64e2905d6081609c308cf9cf5d44f2c5a956459adb5d69c6b8eb5f07b14f5a8d71863cc6cd90b53d898b2861a9359fe20a4f4bdc52cf9ccf082d2beafd87b90e3713752548adbb24f2efcd7deb98714e3f1eb891371190aec8642948ac9bc2567e85b9156d1836ae86776da75a2c37a2a7ada4ca28f0ffa196bfc987acde717fd8d474a1f44eb9b4a40d12d21a0dc693dc0e3461b7aebad9fd1aa97f522cbf2f88dd1e6ccbce5d7a296c74133d64c0e2965474a5a553e48be057a91168d3224ddaa322018b2e8d328158882be1f244155a88a2ee784027a0a9a2fe3b497dff9a407d50b5e445c1ad32ee845227bf1f8bd8460f347fff2a0a72f927434fac9e377649df40dfd25435555e704551103bdc8de30fad4fc50f5cb20dba6cd89dfcfe337da8a297cc2d3a59e5ad4f72c4a75418a88db96bc634fb503b487f58a7b9e4b782ef5ecd5ce32fbb88060d78b7028ce3b0c463018d27cf9293ff451d6d1872aadaa7f9114f4f4df5c52bd941f921f1bfd50fc51bd4452e641b20a8dbe7af96f2a0999405fd9cbb444f4b17a6a2f13c82211d18f96d9c7f5e456cf97884b5ffe0d93f5125dd58f2acbc36e94f96873641e64735ebee261b77a2a13aa5e48d59c31cf5349e963be14132959d5981015233f14f3eea918196360622c8cc37c5561424f29a594caa746846c4e7c197aebc69f39f1214ce22fbff110d0cb67b709bda83669afd0836a631fa19fefd46505d50b880bfa50c85ef2b28f91bdda1dc9c8d80bde16fdcb933ef4cc4364bec47e1ad98b883b2ad9abdd908d7999caec23c65e4feec85ea0e70b53775e1e5479d885fff2a21c277de95f3c2c3e49e9df3884f68a2051bde09291e847224a299b48441fb2a2bad371dc10f50e7a8a647ec83212116d5c1e074df97f661f6ef517bd96357b329931552853b2ac5149a69a3833c68eec0561ec456f55bad54c0e28e442787a81d0074847f39f88961e731fa2669e3133f8200b998d16fe4ce5d924c7f3ccdf384d35d513ebc1defa8bbb5420ac07fb1c0e2405fa83cd80bb71372cc6a3163dd15f609e54fad2cccb7c4c8e8f11fdaca67fcbfa1c55a0b052a5892a5058a9d2c448f244627a6a2f1e379a4b443ffb867ebeacd75f29a50c85e6d3977f4354078fd0c409e9601010214042a1190a85422f274e88521aad37d52bf4fc3bccfc68566be66f5c64c9d154627ad187ea682a91a19035592fb2970ff712fd7c6a73a6dd8980693efdd0ac0c79441d3c2c1dd557ea07b0a60e6b64593baa58f62afd5b55b8077bebe1c4b168e668eab057e9ce399fc65ef05af341f6a2d79a96b54222eb45332f32cdbc6573667e647372849ece58f653e9650eeb7d22c9619b9f4af66562626094c03ce961e44709635592244918d2bb7fa410e985645ffae5adfa329f524a290e0d3deda1df77da1cf9216b42d2133469c5970cd1077d652ff934d4b349df1cf161d2640ba5f6ea59451fdbf481f5985de608082246b69b987ea7d31901d31333fcf36472c40cb57b6c6d293db14aadfdb5a940416d3701624e0716dcc7afabf8a907d5ab880b7aee1b406f49f88cf42fd6347a26f2f2375e995461554d9c187a58e94b2d89ec056fa431c608dfdffe01025527ed05e552f16119b47aab0c1abfa703660f246b6da1d5bf586b0b8d96fb87457deb1f90b2fefc84a79f30ea8f06213858f56b54b9f365f532bed25b339f43c7ebe0f1346fdaf1a6f9a39f2ffa38ebbfa85abf230929ddd33746f24402f396bd62e692d14f215a542fbfa298177d8ca9a618f9476efce42361644c65d264cbcb68e2c4c0983a9ff660ade9851453ada7b5af0f3f748901c98468e8633e34716248150822588d18904ccccf89131392f461ea259f7f071e1f9a95f2f81ba7f3453153c9ffe863ea55c48df998a944f4d6c77c94a926fa237b1571afd1cfb76c8edd89802964df08dff996adb3b29fa6044d9c494b5446981f5598e7d900fbf5659ea704acfd6a4d2fcf44eadb7a7a59bdd42a84f56094642f0829fde71be6d31eeec19e3e3571284e08f6f40f187b15714732372ca65ef21f7d9879a42f8f7fbbc3093a2d8fa73687c7876cce0e5979fad2b77858f6938e17edd8a16386c664caf13c95cc7c8e9fc9112d69d51c73f4568eb7e64c8e776fe5786b8e72585629872db9d25bb56449cb7af7a28f95a7aff56ce288ac9f1f82839a8ac8af5e247f3ee8db5ce2e4ca9f55caaf441512f9d5fcd1080ab5178fe823cc8c7919197b794ce5fe11b2174fdf4b088af42fcf7d43e8e9d39f1faadc3fe6df305a413faa7c67f5ef61ac93be22be2f3567a7882b123de8b967928944f495f52901e71be71b1755bf970c89ecd557f4a37af91dfda4f3ebacdc3f467e47f59a4f855e54fd46367bb0caa0f4ad32a8e8793ac07eedc15a6b0ba55fadb5858a2cf70fd0d3eafd0314b239f2fbaf6979fa4a5b3dd36b55be54bd5a11971d1c3b5c47972eb786cb5ad6c07223e95d945fe7528f7af0225dcac58aa2b132e95eed9fd569722f7e1d5293cbeffdee46c80d9998e3738cbe34414f1f04aa3094fe8cf990131e17863e08f455bd48b7aaaa17bb03940b03027d25fa987abd3cffe861405534fa1ba7f502bd0804f3540412893ef4f23a2491bd746ee82f1dd0df3050bd5e44a3a7f5a20f43ad0ee98a462fb239a387b139f3a90ee95290d521dd510e1dd29df47d2299b6f929877d173fca483f521963043d15451923481465a29591f9aaca84dca075f4a27a91aee841f5d281f9d073c8d287a9ec276a2f7a29fd502826c65ea4073dcccf54f61e3056f283ea7b52bd74487734fa3691f81d7da8f61d8d46a3d0537b916ec86f6844abe8197d91bd48975248d22191747448d749cbf88c4164ffecc5c3b5bd78ecd8a143070d8dc93433932347a92423131303034322bdbc8c462211a5a1100834a794315a5655414bdd27dfddaea4bddeb5ecf56e3f45ddaeecd560e5f79e3db5bddae5c3f5eaa46353658891a69f6b2e20fe27121b6108535cf71b38f1e1ab382d5fd8a2e50b3e5abe1004125b3542978dd0334553bffe44fc8ff070a229c216a45c7f9233a1858d1e2d6c54b9243f6dc1baac850b63f0a14274026cc1ad317f10c5ab7c74e0e1e6c8bb800ca00b627831035a91f979a1a240513eb0ea02973320a32aa7a890518528bc000a53a0c2441902edf66cae2393b0183b1063078c0c37d0b081fc111a7103d0f639ece1bf6757c3afae05bfb2ad1b5a6621d738bff2c6bd5a199a16ef18638c31c6bc0d26dd99bdefbdf75e9cb99cdff7f3b339feeee6343305a572cb0136234787c39812c694b65711b79fdd6194e5009bf1ccb89e84c360c175db3cc46ffbd8a26d7e621ff25b93ecbdf7de7befe90079eeeeee0eff55873befe173b08566c044ab80f20f09bab8fc13c5e706b9fc1345ca7d5c7bf240f9e70835eed56cda9f68701b7682db9ec6833491f6851b516e7b526b2f5b195e7c6096db9ebbbf27dd1cf79afbeb55cdd9272a341c64ff1c7b95ed81ae551542af4318481346669060b22575a1edafbecc0c048a0c31b8cde655218a2c70e15eadc1e880b6678cb927dd1c0604ca7548accb22b31c6033ec13d45f6caf222e2be12a5cf6461bcf9e5c2ee2b2bf58cb18687039c0667018538850b6a7ff7a7299bdf82f5849dda4db3400693d704861c7efd8b967f6bdf71c63ecbdf7de7ba7ab7322833137ca853e643b9282f65c863f9f638cb9477df313e4f2a8f79ca3ace0ca973d33e6989bbb634b57401b7bd760141863cc5de301f482b6676fa011a24ba0ecdb0f6166c73c437f40bdfdfb36b9b80e233619f76acfadf970190df7727767b32da90be5f6576b51b8930b1a4d78114712777e830edadbf50272fb44bf9fbb67ba07addc3d42d6832ab31e57b33c4c496b53499b56d60a075220499c183b102389db1cbccfde6673986d563a28d2990e22eb94ae73a07473d8b3b357a95690e436243fcd063c163178028e24c6a0c41a0e30c0126494610413de380117442a7be3eead6cccb27713dcb3d69afbd65ecd69cfda3377797b6d854b07ca155645f1a5d36a0e7b77f6937b53922a875bd55e85f8ecb92c20426fe75c4e7bb7de318ed545a2117f66e35b35c759d311befaeec0d2567f5dee9df4b59e8af5fd7b483973d6c5e951d4a3a8778ca2f89f6d7e7a1e3a27af81bd6a6e94ac3dc369ddac4581f7bd778c9999dff133da5b88d19d07aa85b566df8d75777777777777777777777777370db2083f45a0ee10569515236cccf487feb0d6ddac779c52121291cae587a389cb96c7af01ac0e91c403dafff7d94f01f0f10208baa581410434811a3c0c0c0b581374b13c72cac59c80d65a6b24ea1a768921ea41a25433571c73ef55dc21f61e4551b239e728244dcd23dc3788fe24f82afcf7b19d31e78d72ed0992265d631036d71ceca78e7c6c676f3e35e6ef8111a2dee57741a2d086d683defb677cd0b37aa5fdb48d31dadd21cbe7c710ec20421daff456f1a97f0a71d97b0b75286421fb6890757b0d712bb30fab3afb68fd54bbcad0498921e841a2c8e7f656fbeadbaa57beb28d35d95afb7e20b8a1d682880d12717d49ab86b8bea4dd16a36deca3bdbd4270fd5bfb57bd3277cb5b16cfdf7b95970d81afc5b7fcddfd7dfcf8aa136f3d3d41fc78947cf159c7a76a0837ffc6dbedb9a45dd7e6f3c0ebd8c7fb0782e8c4ef9cb6b18ff71dd9c7fbe7e3596eb7b94db3ad5d28b2db14916485f6b3cf35f1fb3313db073011b7fc84941c88b865d6a308ef1c781c149c38276e3241889039464dcd6d7e6388e4fe17aba524d0beed7e5d859637c96dda6ddfb47f7407f911fbf0ef67c2187af8ab36badcd3652d6260c28d335a35c6ea9779caf569c4c2e046a7722393c18d6d89fb2eb30d1bc1165918e3c66f3c447e7c67df75eee6b877d2aefcaa2e717742577e7c32891bb94740594b1b6a5cf691726635cbb29c653dcbb2dab22ccb8233a00b4a13377e5559cb185c7023bc57f50ee2cf01f15fd5bf79513f4120d94b40f68272a7bdf8abafa2fd620d2b575a661f3186f8e35fce359b533dfba95ed5e4018a164aaa67d5337eebfa5721a040b90e6de3d25a7b5860e62e1560669e007305b8a53087008e30b250a10b152e3fb0818d29500ce107681cb10593c25c816e013305780d666606837901332041195e3031a50b54582982119478c112a8e0858f09a0d4200c2ff08213bc38420aceb0610453b250c20a2b9a885202224d788218ac48e3073f6590a10739880112a8eca008616024a8003fed013305f8e90a2cc04f3b07156016431b4e5441230c6258498110a05071842a6869411a3b50630b16c502cc6f1081992b109905b155b4c009345290841f1310b9fef611f73adde63e1c11650a26c018c316de20d2cffdb09f76731183b05fa1d7cb65ffc243fa861912177ad9608310f6cefe24aa01bd68ae1814de22a4dcf63d1de04488e81b7ccce06e2e6019030c35ba19fb6fdf3c089751051356a6347bf1b42b671028dfab35a3845c8a3a51d433fbe0504174e01efe25aac5638304665ce1367bfd0f3acd512d033e52fc94822264a10ceafbf67c14d51a14aac6cd3ebab897fc2b86292e77e1083138d6ccb1665538d6ccb1665538d6ccb1665538c79811ed9ba3d20ca4dd996c71e66cfa43f7861d422a6837e98d290ed2debf85ee939a6b768d31b3b3678744739bd113f4e29f19e8c54c388d214813118395eb5fd34304fa86c874c45cd0e57f1d440ea99c7de62e73bd8808ee05e5892b6e7fecb9c35bf48ce0f2504fba94bd3ac084b9437b7a487079a8ef2b8625aef56c1a096d717b16e133aee029d4e0f68f61e50952b8fd53a0411855f0119070fb190c24b0176adc3e42184f2083b34851040153b8fd9129d085c406947cd3c88f308a9cd6b05428c22307272fd84881bde882115118b7ff71a08cdbaf24c7a5ec05840e4434b89405ba3cb0e5f6531e704352d3086cc2ed871389dfcb89153b5847849ab0030f276e9b46a82a14792fb8aeb21b9b7a14f5dea3dea3a8f71e4551d4a3fc0cd0756fbfcf1da01813c9904b5156553dac8c7f54dca37aa7b9d55f416ef5171056d9ab7aeae70e1ed01cf7f02d257d2dcb64e6873a1bc4dfeaa9bf3c8dd47077380e38ee3472d95b3dfc58b50e62a7e3b8413879d77a1edfc9e15ab6d907f555f5dbae074a97a7749b59263c385feea2e788db031af42f1e830238bc9f6aa2a0fed30a60d872dfbbb7ac3351284f1cfa034aba6f94027a01b90e862ed7f9e267d573f84ba7a9a4efb3333f949ffd7fb3cf2ccd0b827dae13bf3bed46f390209cc0eb9ee75dbfaffa6d11fbbeaff6757687e388e3369b248b31fa075f24627ff7a809a749b02f9ca0b931bba0858d2e0dd996d2652d6c04e192fce4f04a259b8d262ec94ffd5f60270844c87785cdfbbd47d9f8aaeb70bb2ba1b5c656ec5595c51819ce587bac35fee720e5efb5f69a0909fbc68c3d975516188f61adb9b7d6dcbdb91128b7ef1bb735f66a40d710b759d8f3ba9b438a51cf3187105a13120f859ad933ebd60d9d0952cf756b8c3566621fdd36c6cc05005d437c2f612eab764a68d89c7bcdbd67bd5541aab5d75c63e6f0992d115d5312f5af618e81d800c46d9ab40a44e5f11eec67fbfaa4af4b407eb44faeb8a77e470e2813b1883091f88c893cd185a4b6e48b95843309bdf0a39c45fa421e3f5913075a229e1a40b80d75b459947b4f3df81ed57fb9c9aaba3508df83cffd7b671f7f1cf110e1363aa359c46f067e4082f2532f39deb8d1e1080b129fdd587ff01eec5b8d301cd337788f0c558aad8a8240f9675040fddb9a99edd98d95081c6a5d1d3fb1ffc14fa22bf4e20cb80dbd8cd99884e020c40acffd63c382368d1c6594684e349cac600283698d191db2b5074d9c066b89faab348b701aad9992bc87561ad5b7673a6691ea1f182ec969e692f7f04951321925e8b2c09b3e7bb2e92ae882e244fbe79aa88c1efe86e2df2667cebd4d15a45e63cc19638c91bc2d61ad8b28ea01fc089f84854d23af480df73de79e255128ae7b8a12eb214980c510e2fb95560431368e7785801223007b9114b43e715f844524520ee0d943fcf8b050f161cf226c85229ca5480db7fa1b6ff66272ab2517935b7db46483d041aaa9665445217c09a90a63854592dc9a38b4073e21840fdf83f582d46b97f557e526cbd4e09b1c5294a39c7bd83f65ac267f283d3d3d3d91c167b30867a9e1c26f1389452d3102e456b0c910455828c96d9c03413c7245931047dabfdf816ffcf74210e1011ef7d99cf8ede114d43d1323d6c3d7b16efc589d21851260dcf83c79e033a450c208375a4bac5b596c2e813f9945ddc6faca149fd5d15e4f6efc57afd6aab7aaa9d92296c999a0897ad6ce934b7d5f8ba2fedd6a26814fb11d76ad67138975a37d5676f5d493dc86ba20e020453d2854534e44c81060f29781245156af3651adfdc98d7f59a6688b5826abb546bdf7fc7d7baf596a491880a28612d2de60be749bdb5e3e1ccc97dfb33223c0c55f2c818acbc3eef329a6c0019432c06083045af8a9a2837c9f3b40b900eb201d08f7d99cf7423ceb011eb77dfb213c5163b6ec96f2c6bb5f4a59f9f695525ad64beb65bddc4baba75565bdde5ff1e5b3de144d95f5266849d93b2d25f58f7a2b864451d01ceb5bce553dab4f8ee838885c915f844584257177be4f072841843dcf2639c038430a25c038430a2a4498088b0fa8f52cfe8d5b35c7facb7a37adbd98dc681ffed5b05e8f44f2426c1a4a6a01bd646ccd2a949774ecb9c6e092cbaa6807b4915092112097d91cecc3bfc43efc3f4b7b96c53a63ccedb4cf4e6b719b868bbd248558bb6f936b7f02d9bb099a5efbce93cbbc355fc62cb38d096fdfe06582de1072bdaabf2853657afea6e6bdf3e476f7fb27f1d6ceb9ee9e4b9ea57389554d2bd49fbbdb08932e085c675f2e81df60160adfdf2f7418c4cd75f28280d2737772b8fdba180025b117f180f643b6c20de22737ea025a39d7acb5b9c47d7bea99c985f0dfd770498a544396cb56b87289b34d8b68c5b7acf85f8021894b5532c6df61377e7cd6fcf8aa69f295d35e4feebc401fff5593db22d635b12d625dcbde04b2ecd9bd376cb6ac3becf68cef6612eaa3bd42df987dcc8f3564afbee23bf9a42b7e7bfe59aff857fcea2b59dfdf30be560eb4a624d4b7a72cbdba59923c23b26ef1c676ef1b38014faa80b216387c7a683d3d445ef31914502ec209acdc8b07650870ec3202b43f69288cf914483e803f88ec65a341a025ea0329643c418a3ff4a7fea536b8fa8b99b955b5baf81d12ea5aaeb6190d36e8f87f4e80c6e5ef099f8a91bffa56f9abf6907aaead55becc54553145b987b5e134232e8a265647b6196d46114eb04453a1bbf40fb64d9a0a66b41949b4194d2245815e247f1a803415fa06bead9951067a95ee6d4b50f76928924535a0221a3c331e9204faa99b3b638ca901790b90be1723f1dcf64fe8377b555b685f46afc0a7dff2a3ab5cafb8b7adca8713473e05257cf80ee14bf96d567c9a425cf96d82e0b72040f0d96de037f9cd093b6b9085ec03c221420c6d93f5af5573d8b7aa7af8d5965b13a9ef69e48740dbcb674ad27efa5b8344024243cd17711458fd0305ff0d8efa87ffd3fe8182244477e6aca473f27a7595cb7ab88d2e3d684851a3a7b48106972e42840061eeee5f0eed0371c411873b074e97d99c7ec7c27da64879aaa1290959e2724f9092271321acc712426437cb98fb085982f65f42a634a1b51b1c6c00229dc5b71e54679595598fea9959f62283f66d0eb79458e39fcb04082040800079ac95e2bf4719ed1e1a3831d0eb927af8de37f08841fbba66670fdd37bdfda335ebfda37da45c07318183edf7a4606fd74b0138f40fff52c90b18c675c64db00a68298f43ff6082f65f387469cd5dd67b553d4daca6aaaf554d95fdca922e880a94c2ea0d7a3dcff1ef6fd694c4fff1bdad32b7a2e754435322bdf79420e5df2b879b03fbf0bf4ad74ba512c9ff260715d0fe2b87ebdf258ad07b2fbdfac643e053330ab4af731f601fef5fe321ecbbff03ec837af661372e7b5824092db4d0228b2cb2e0820b2eb468d192254b16221081084000021020433684f6eece3d7f240c40e263851556400105143ce0010faaa8a28a28a288020b2cb06833de0c8b533354bdf2be5df3810f7c20042108010b162c575c71c514aef9379bc2dddd14ce357f820a2aa8f0e6efeeacdd5988f2823af1eb56dee01272ec7a6e8402da3ee77d3b31ea6ffcea2155bd28585dd443ea3dca5efeef8dd8cdcc1b88ccecacdb0e17e61f22331c589835e09843c8fef93fe7cdbda3d0eeddddacdb8efb76916e43d8fca3156585375679ad4aa1aced46792dcbc5272919e22111242f791d7ee8e784731ee10b2dbb8f59fbca0a2de6edba39c61a03f57b23ea3cb7d7f7cded9a4ae805edbf484ea612f7f67eef6eedcc1ecd3730986badb9bf73ec278eebd694bb6b4f2e7b678dbd2f69140506ebf1e1abb806b134c823a7e8c806dddf4356197c5565f592336ce2c0eade30b653843fb41714d7ae2bd2260edfee5eb020449a96baff07d95c60ad9140a75001758526484cf7e820ef1d1016e4d91aefe1de3db70dabf193fbabe6baef1f15b82d98e632f7ff60536083b42a3d3d3d3720c24fed0d8d311f838f0d44fe74ea1f8a83902f6c68ef20b0bec19f1bf3eacdbfaf237cd14ad833bcfa59b3ddce9c31c6581377f64c7a68e28c89a5a1ee477880b4c4cfb630e9a1090598535298d2c299873077337777736bccccae0d4b7dab37366c6fb3377e628a7a1445bd7e59220f80f2b87d790bf35fef79873f3f0d94cce3bd77738499bbbbd9dec73830ff7f6b4fb73050dfd0e0112cc04a2c88535216790176ecafe777560766666666666666e6e66666662e22d734890b2a455c60c039ca08146ed136e04683b0efe60ab5bcc07673e6026a6f6bf20bda58d4b88b92685173f9dbf30dedb9e6b9730c0710525a2a3d680cda0f25859036a43754422829a496115244d313ed9908fbc6b2907c885b5c07c618638c3128501e8e88b8a0fe593e3206faa1cc2143d818fadcc0c60d32c441880dec7fd07937888da6d12edea3b57bbb53c9e2bb534b502a5da648ef61de549ecae9b2d69a13f4006d346f2fb1a04dc56d9668020ab789dfbcfad65d588ff66dfcdcd69a658fc0b7ace88cc297b2dbaf5fea61ed2eac546256073fd91081d03f01fd7c9e1108d92b266ef609e8456004e8e7cf9716d027424fa1111bac94f5e82ab697f053b3aa50ee1f94bdf1136bdf840501c25d5a63c27a3403dcabbb9cfcd4b6a0cfd6bc9f9a280c1aaf6283d8ce16feb40664a1c180bab2c5bee7adcabf577bfdd94b511445b9a728fbde5ff7d38e72ee4160942bfddab5a5f1937bca96fce44448a0fd350e0af3c6820fdbf2b1b5f73ea75fc8cd430821c584dc3c4bbaa4fc39d237f4b39f23a6b8a52d2a6830004d618b1719b81618a165a1dd1cd181bad1e1c8cd1109dbe7e70818dcebbbe8fbffa2e7ad4b7b0dc86d8cb1672f793758e3850bc36df1eefe7d757777b773ddee4d1c77730442d7fd2aa05ef02129a471f013f31373e68c5ddd9d44c8cd2b954a2da45f080e37540bb969b72427e43208f9fdc78830dceb146570afd216ffae0e7eba3972dbdf1c693e374720a494d123ae83cf8d0e47281cbf39626f3bb741080e3f466cb94c4bfbe7bc8726ee33efafd6fadb3369e2368d6adf84c76dcfa48726b031f120b449b396864a9f9b2d477868cdc7a0ed5b6badfd18b1857bd1bf5ba879efdd9acb6ff906fff76afc9db7ff1c71a541e13d3e97b5c461e5feb8ac050e376e57711b374244956ea28a5341afb6d26ed33d0d85df38b30f053c033a0e44080bc2442091fe236caa71a50943b0624a4f0f91c6f246fc2103cccc8373cc39e7dc4e11eecccccc5c88ef23554541a0250e7df5a121384853c17abcbf388ecbfd6382dec2821afefa4b006a00a11d231ea297d7f1a4e7d7c16decf31f719bfa315ff3fc3db8cd0c2f13001b003c3fff0f6e2380e7d7711b1a9e1f081e027afe1dbea1c7f30bc136a7fff1fc3ea365354e15b970e1c22556115b4ebeb1ce5af3b1aa6a00be0ae02b159d0a4825975b5d90f52738152c8877f11ecfdbb8efa5bd4a5ca818dd800601404a054003000480929109409519fa5d9e31b6a609daae09056aea7c8f5650ee46071f263df096233cfcfca851bc8b1fe0c7d750a34879223fac9be13dde932ea4640d4cffa0838611d76e7c14101b80381f32d045080e422a45c4136ea39235d592ac16937dcd03c1c36a9c6838e9a8a9a97997a20229225953ffe5b38091357ee3abf946493442d713a587aabe6f55b19a7713a78606529494949c1fe7fb12aff2789f4678f090b38e94a0fc33022e977f909842ddf90c4a276b4f79d6fb382194159cd37a5955935541cb0abdfcaa9a4222d0ac2acbaa2ccb9af36fbc9a9339764e4e4ba71d5d111f196e52b0ee301184b39a56ac20082402c1592b2767ad9e27fd1ba7646e3cc61b77b2aaaa27a90ad36db01eef21a45454cfb388888a120fb97ad6ea79fc8ba5f91b8f3c71609eb2970e0d17fe00e45314455194a4ac58137b521f5d063171e0c7cab7aa702a5945cb64b0a48ad264cd9937d5be7154f9c628797c4f9cd1bfbc8d51077746fac48191d2fa01802cebe74b29a5943f432f0700aa89098000aa3a82419c9286ea5d409200d5c71840753342d5d1d8513d0d3fbd9f41761b2c8853e11eef21a4dca517210c78ce5828032bdff805658d48f94ff8f8089234d6889c89d648e8a34956750403fa2eff2041e5b2094682aa3f817dbcef511d0afde3fdc9a994fe95fea21704624b8f7de0a01469da1740d2444355013c9c46a8006aa0635efe41e20a54878c8c0c40ad1e003236d831124366474c49d6d4ea6788718247bcfc83c417317fe391873455b6a61118eb5d66752e567cc80a988f33d2476d223faa77613dded75079def8c847476916e9fb848f8f956492b6565f495a5878f9c7082c24fb4223bf56afe3258b19a81bc419a4a972d42179d4ea45d58eb0e0aa8e6440dbe51f246870470098951d5d412f0ddf99dab76656562404da6faa7c7feef49df9c89505f22ad8c7a3817158a6d0f1a8601fefbd0aea5ddecf70711f0309da6ffde55c2e8c3b440f0a2b50dcc76f14b1177e8cd554f95655f5d4c4a976e8f8ea75eca85e87fceafb19cdf7653450ca9755529e385557a9dd849fde9b2adf191754d9a1e0472b63ec8fde18630cc6a7fcce54be39ea457d542652aa54260686f4329a482a52687e9cb1ce39619b22252fd1cb6a1281a64541539250f5555f49bfba30bebc843e147aa7e2361dfa7e9889132a954a7256111028ffb4a08b7b791c5c861ba370e333a4288a82d5cba7a88aaa9eaab25ea0bf26887ad33459a648552624f0ab87d5572da5bd86b8d152975c5241d623008f00655b1a4ec50e2768739f3703b5b95a1a9771711b8682ebcfc6f02d6ec35ae0f8c2f5674c48a3a553a009131e28a6a87f222d684c6b0d56937b30cebfa49bb272af5996654d48dabb6f38cdb144a79e7a1d6e84d8c0ef56a83b5253a17c89eef9f1aafa157ef2f12e1c0c2ceecfa1f093bf5bb154eec5b65c2e7eca82fabf2fe14df8143ca47b6c9863694d0dd9d108293e85a7b8fd6cd9db5efc3ec5a7f06066a97136871280b6c818638fc5f62c947dbb246bac35d75c6bcdb5d69c73ce39c8ecfca22c64ef0bef98f71ef3637ecc7c7185edc8b8f0775a4f0f126c2691b994bd86b8b03d7b52cc7bcdd476188ef59e3339483516a380a8c4cc38943d2b15d188000800005315000018100e078442a170344d0441f21d1400107d94486848178be35994e3400c21638c31841002008088c0cc4cad03311dd2056ec91f72bc01527b65990312e7ffa178c28e446956b5c3236818153a3e8d403583009931b462ca6764f38d899c0b93eac8d66fa04e7a92b30717e38f81f395c6e3fab24cdd31d2aac41746f004e76cd2b18a2673e21a5c9d5a7a357151b38fdc92ebe98c2cd0cf34c975aa01f2b0b96f57ee8eef7c3f3ee7ee2e5a7a691d38726c224046bba494aa56d2cea8e70894b5222a5c209b6ea2196504781bdafd2c71ebbb306b040d8c8e6f22efad33cfa383d48729b93346c8616e1155eb0a66475219008032728c46bea7b3ec8c0b44e09bfaf87ea340de6c06901f6920b05cbda383cd62e772a94474343d035a0c3c5a067d0c1643b7eca6b46a61ae9afb2ed3950b956362ff5678a2a2f9cdb0c7583eba411bb1d5816c5d4f9fb90e6292263f971725a8601d1323d393433ff967078577ffbbadf85cfa2eba312cdd4e56efbca6c28de681254a8b490456afd5d63900718d3ff1f150db85851333f7cb984a1e313a4be674e812fc756a5fdabc457266cd86ec57d0865220661471cb740be1a02e8eafc96cb44597b9935cd1432ca18fd4a03a4d694a86d7b460b9f325de0405265468a6074570b8c1107ffca92542ccca9c8637ac7da5221de9ba7b20782b88bbe5ec288051753b25e61875aa6912dd0c8fd355bf0c04329988728c1232fde2fa8640e6388c02541dfd92bc5aa143d2aaec053f982ca69ba178ec5723f5900f97dc47e751b66ee0c3ad3c8c1da3fe90eb32db78f5cef8f934849eb7784708cb6331bcbb8c4376efc56797e35949a41ca7250f2e75e76c341c2e40b11ac53a2f3a46201d62f8a137c786537e46a437e3b239fb8fd339cacbb75900a38bb6ccf75946aaf9aef2688fe0b95f7ae2b662739a9d5378b36f8909aa29a2823b4f99a4da65ecf8f88ca1f145fd9eb34d159b78fd4aeb0968583b3cce5f0bc2c5b4a45f6c39ff9cec30f1e7145229cd0dcb81bfed376c36f8e528d122ef2537d3715828df5c7ea37e51fd580d8de0aa8eaad95679ff394ed3eed72baa2ca85434b226f11ac673e47c4faa387128f8f8468cb8e3935a9517f6c05919a6bf4fa97d0e925f1d06968c93a7019922a0dd778ca6610cb89fb58c2dbc50d543b5dc409154620cb89207a7ebfa7bac3c51dddfb14398c1622a09be8a81a716bbeb8c1694606abfeb095e30aa0628e7053e8c044b3cbdf8670c9e8399a2f3e59712ff7285a23a80796456b31abfd339dfec477efebefded2de93cc2b2d81161d6c43bf8b333c62cc48ad77b0e7b2e1902a062cbb58cc415970f2964504564826e4aff8bccf3fa9a22a2678c0a026c705f8f943013b8fcab52421e6351d05a062f143f269272438b196373569851a3d2a5678bdde598f649bf2e9f07a24274eba89644d690ff9675a61b8053d7d2e26154fb2d65d85c3f8eb9eb5720466664ad572c2c59f7dc31a645d716db9772fe16cffe87ade472810f789b64074e8d51b36d8d64148c8852416ad3f69860e93d0cd965e9af5924a06bcb5fbf44df9a9829c7cb749fc3020755ba42d0ec205d423fab1fce5dd866f9234c2f7588562a9df3e1b138ca9971abf67662ea1659651ff481150a63b6d09cada8ca9dc63d15f6892cafafd8d82e80cf34c99110648e1324a761e43985ed4a605e9cd7272f655112840e4b592d92dabe7a15a077340a490b8ea3d4e4cabe66b8a318ccc5f0558a8037df21d8ef19babd3220384571f15f449199c77d39164d5d2af4c281a9c1721d4b52b31b6d419da897fe2f3de02d4200e74f028ebf40c6926a004ab76f8e99bc1f70a97382f90cc9ee60198b2da4730a3b47ac2f0b229f4f84b01218a6dcbedb8badbfd19a15eecc1ed820fc4066de0243dc4ac158df9aa45c07b5d12cba6dae52c1d195696f9f937570e06104585ed8015192315e1272733dd44a26dd89ca2db699ce4ae18f7fb93cc8240b68582013d6d01415dc67bfa052200de20d11b4e75cdc13be91158a84d9e27a0b0ccbd22db65745d5f8f377b130d86f5b198aaeca7511367cba3148603f07384011c2ae3da9f273731a4016e7c470f7c714d6f36d901ec24434763eda0da203cdc8d9bd99ab17e8c8db09530830d277a23c07faecf2cfda225b2fba9471cc1b7ad1289a99d456cb8dcf93631f1dcf0febe5802231916fff980b8799cb058e3bd17263455c735be7adc51c37419c6b3231d848835cab26df82209993b050cabb67f9ac45a1a3e396f9ade794ebe3937dc2d4fe11d8900d147b41b17b8fc03ecad5eb63846c290d1418b0fbfcf638ad5b2e0d5c02af49da0fbb947c4c3fa36093b2b066d4313c1790dbb729cff040e5b72bf639fba1850e4afdc32d55b0c2095dce4a0642a9663df4c1e7900471afede37fe4372ce631f85bd066b54b554ad09eada8f060a505ed4b698615b0ebacd6eda698604813a496671797ab5677f3f8372764c57b07eb6ca266fa98d486724f280a7883d1da0905e8e1219e5c30f56ceba60a60843a591d6a7ebfe78047b9a143f17822331c501c1068832817d6221d5ab58f82b112557aceab4e04dbf730c5c2a2b817fb13a5979d808c225cc65a470c3a05de827d91d15b5c72199d9c1fb893fcb9c9dc07a943bd11612929222ef45e77eca0bc1997702a80b482e0aa1ce1647b199c4f6fd4229c6271e78376ba5b8c71cc89b5df0278a88fce82989e918b208401083dacee25d25070a566cb05bffbcca605200887311339f6594231bcc3023ba6b6fdc922ced4927fe24eb450edd5c445b7b37ee65372c072eee5a0fc7f3129d551a7910257b55d872a42760f9b49e48bbb1a70d279e5420f08b37aeed770e54dfd248f756ee07623df9a6ba5f9ca2762b984f2c87ea48464dcdef3bcef752325187fe7bf87aa4314e8bdfd22f1b78ffc2c9161a7db967be6d361237b6aaed9f5d78c98956a008004e9804afa00ecb537aed93b9be4c1f4fb8e83119510fe949a455e6d1bcc356b324d3183149b39c7918887626a210460f7130b842d8f190ed384af3db8c89802b3df217c257a7257da7b296768e06b4197303ca1db0134ef29992b9f1b246f1088807ae839acecd2ee8098afe4948a8e5d75ec016a8cf1198a64789ca366080fdb8aac07c8eee08df6e98598d659603ae180d8676f9da80fcce8a5c6e3461e0de01d52bab2b3cda24710af405fca8c1fb3105fceb0280293bfff63dc5b3962affa1160f5f605a4b5b04f6d8c231780f38150c247b39111ca681fb58a23520ba327e825c0d25a01cb1731532cfcf7bb750efc6368368c10c0743a5d7bc5d4124c1d6ba5ffc72007c23ec7304a8016f99973615a3853d8b8c08d06912955465acde28c8ee706ec7bd7562ee766194d0ae020bbc9b43bff35a509ee35a2d825acd99cec9feb0f717b74b8ecb6bdc061b3b7e4f4272b77ce72ac6afa75643383075026f904805f3fe0d14c0240244c48cfd79d339ff89314d373e12d317b2ae1d7bf9faf0a16576c812fdf4b7349a7a22c013a8646d1e8dad959eb96de8f2a2db1f33ae045b0a6684905d48faa93d516261974a3a0fcb6f8cd9f9a22f4952579930beda0f146aca163cff28dce92f58e51142280958fb20110dcfbeea6c9dbba1ed828b194212a150880d51fd9334e3f44173ad260b57de7ff3f53ea14b02d42f434c7f89c013db41266e82e29059c051678acc5425cbcc66fc0b8d977a166773a2690b3ceb4902f1cae3fa0962623e11056b9e5652c0f989e41369a6c3b550f88660e1be2bc60db6018c1b17af7f4ad08ddaa9b29f3d67df47d86bde493bf97ce0a2e08683e15dbd39eceab727d591c845f1e1dd9785269a31d36a634192b84a14216ba48d47aa03b311546c456b5bf23ddec8206d50861879b7b8c823f1cfb5f908f2355b03782557954f8ab643190513f901f7109209014758c7f48555d33a82a282de2c239140b6f0a70c07fcc3ce9fa1b3b10e015d388ae32dc33eb56d5aefd2bf40ce3a12ab234bb98abfbd4f86351a6c6923a2382de298fecc6d96ee27c70dfdbc6d3cce9c9695388afe0fe138611b8f841864046c134976f4501f00729fd7dd0d3bc3331c58a1a36a0a0677ecbe6ad320a1fab4d93010d19f9ddd9d59ec4be8b81ce02e09130892e917e1edcd56a279992702f97db18ba62332d9513d295e2155577b2b68953e8f58abc19c831f42c167e2c3bd800fdf0b7562458278506e8562a95362d32c393e503fe295e7afc65999acc0cd8b815b11d4f944b2686fb20d565994ccdce9dec053bdc70c35e67465d30a65ccb3996955d9003baba260a025769e0af0506927a26f6ee87b5dfdfadf682d898fa0d7f5b03a6e36bacd38cab2136cd3132786dc30342b83a8b5e6b0c65385af6dd9cb0c75426401757ccb4de1e24ea3e785cb46e72f9f7b126abd267a40d1ff94dd3cda86e62ee08af5438d52f1e38560cab3d88629c655bbd884f7a73c7dcf5caa1ac36febe13a4e15134f443257ff46d17b23848f961d92595eff252ec04e049d97f81b63afd6c1d8f9c177afcdc767adc4833032fbf0adc92c997f86b60cfb7b7c9aef8b1cf2a4276d29a0dc89aa9d25d5b8d2b8eb51346be0e1873f60bf8d897606e40ecfceb572c87ccd5f4d44e215670d0bd1148aa5967a6d3eea40b1268f242d912cb153a0fb0523b7db6a603602f261c7f79d6f86efcc0096f948dd9b36809f7babe553fe6f9bc03f7cc8a6ecc59e29637e0d7325ac6841af77ca54cc946b4ed4cc02a84b6f57810bb998f1f42a23299c0e7f1334d4350ee7a54568568e6823ba5b08aaec9695b355111cc12014caf3f508502ea5429e65a4232d02dfb28506f1a3aaf99ff03c2fb55d51f3d5b5a7d98ed1ce975ab22bbf5fbc9c8b6843a436c7a550b526e5b705402ee69eb501334f4d7772d37ae0d7ab5f03ef6b8105484684766153fc3726421b49ef0669286a3648298e1ea16f7ccf8905061c842714a54ed5ab1a77457cb839f61bef6d1c248b5fa8a1d90503251d9b0876ec0f1f409254a2c2130a394c66fe038c2a499b21bad3b5f996ab765e2266c29d7e9edaeeb993b0121110ced8e32f9415beca92555124171d80a0e11736ee66ba55f019a1ea4e906638efdf07912428f9e091bd4c14666106b782b309c1cb5610fbca2082113b53445e9f8e228527416173cabf72dd5a6ded79e339acf15b9aa7a40b740375ee9391b8c01b92721082cdd1b719d5d407b9392c73257c52870149ae5d5237eef9a4c9382c30020231ed9c3d491e83a6b95ec4647d10d93aed14fa95e58a48452bd6f8abd23069e69ae750c90cbfebb346a7c9d0d979d678e7145877ba32b555c42ea11f8ab9f140d7236e7410de369c0d49a8c199bb98810bd1c10102a3ca27a9b002d8224dad2ac41cc350c55db7d616fcb09c2425f8829335f98247d9cc8e61476fa09b708218bd246ae06a8f03a236fa0af20066a4eaa3965564652e0c0dc71c0b5e89c0d0b060a874b503199aca9188ac493420393274f169d4ad93bb1974c0aa34177034bc99aa4545db42b7cd611e4699f0d6d4a5b9c6cadcf78a54cfaeeee19beb1f4bdcc81e8c20152b65280c32ca05a8b50b8ce70c5d21da98b16f970ab6eb78ea8b3938274de0afb6f25a0131e0a317d804049b898b63454d5f37140af29a5013544ae15e7ef3a3fdc82166e02d3fb5183017ec5e5eac66f36e1d9f1e6d6fda4ba5e0e7844a001758814a9afed16989bd795997d81d88d43ef98fbed2a545c8455408515963721af58f52f4d5b97099c96452b116110dc7aa9f9f374453c41640c65d80dcc64b6985b0491ecff68c1f65ccd546be6c101fa696a27f41a9bb7a6cc5be3e1b622eed5985118c208afdf5cbe0488e8ab2d7c8cd85b02d7335472622c0ae4aa6e936fc514cc7c4cbcecf9569ff1064d5868611b9fe4d3f001eceb0d498045421ade32310eb359d1f971435fd87edeee19619ce21d39876d56c1b92bc6a416bc1bd4588f13d7fa45c908816168f5c6ada9e00780b018dbe6e78c0d10871b88764c1c3dffe41716eb7fe0f2323d7a2ae1a3d3c4574952bb789a8889920b51c8221d3d4c358c960f136e6c9c209e13bf8bbd74cb8d780f44bb59b9c799174c0ea541e236168a40e85135244da43c98aae55a092b3b621f6dcf2f3c5972a2702d303a2b86a13fef1345976c3eb6ae59f94af116b024d134133d75ab09e6b331c24d01cd18a3aa4190bbf97556aa844caf25afb77553ea172a1591cefe181a58e75823c77905077bc73d7d3c3e87d6260e21748b738465eb7bd25d8447e2cb556382446bba96af7e4b27f1eeaa87e4d67269158d16dfc0f38f8e1a6fd6ca32efcc0b4eeff3cd74aa739eb95f72b5de4cae91460babaf3801498aaef3a7e5b215f63dd708c33c72272ca512c59ca9bed46c08937dffc13d192d4000962da4ae9dc86f89e1235b6d1ac711f432af9f677a0776b97853df3245bdcef70f3dc98e79ab97fd05c9530c56aa1c86c9ecaa0a59772a6a0d82101311d41224c0494928c0744a86c5f46a7f90d7e7b94d5879ea9b74175547944f217be95eb0f65a43400ae189f9f2d8600fa2de92b46764b96e3c625e4b253cee9b41605105d5b1588691318410d2a48762dc48c0a5ca52939206e862463e1d653e415e2a9a2758af5a30e46c8da3ca1a83366cd139d352de92fed10312e9a0db2ea5b669cc4711676474ec68a2be7403c1dca9a7427a340bd8f1b648825b9cc8d67f5306a50a8a8df1e942ca77829f9c86868fd264ba9d6373fd42751e507173d170c60e4a4833e37db513230722be4b61a3fcc2ee6da17b040326ba1b08ce6c9f87be6ff41147043740c85f04386a45e67fccb516590d82e617376dd6cef7d4326981290750ac84ecef46f93fe3cf35581afb903ba040df69c86e996e25cdc35533d560ede05e7411ea38ac75a8a45e41eced44b21c5805d4d3fe3061cdbe3888c5b6219930c07eccd66e140b1aa45989626eca501176c0f1158ee69c5f9457dfbb163010affd69bcfade654b81802c4de8aef543bd4ad2cef5c3f99fb76e11300e1910fe85fda5e3c37e9ce820c82480e4d672be3aeea61f44fef4933c75de7d8b5618dfc22dc01f6a60e3cc8c0c1bd7748d9f4864a8b8a45d8dfc9c87a5e343798ea670ee545cff25201055a8021bee08025ec4db7b883da103dc631c4a07d846a9953b38a14e859de1a8d1668313742b61467ba1ee94085133d081a93d399cad328285318c00520ecdab522021a9a2b4ee485b47cdf2fbb63774b39ddd1622c4d1d7b951ffe15e7f7161a28191811460e2c5baab2511320659bcd0cd10cd6b19404b39abfe744fa0dc96d9b5e4350f75fbc0aa6f97018c206ae2fb814285e7c1c20cb7051ba85bacdfc755adc77c2a5eba5e2dfbb11a7cb8c41e21e5a4b14ff2e2acb4c3c0969652a0f025e17ddc92a4467357ec6fa7438aca4831e7e4f2902f82d49e287953d5879a9a5f1377166806e8ed65531a3a4d2bf4efaad79b2815af366b31e5e863745dec69a01f645deb4621382fc238d1c85d127044fdbe8128a63562d5dad319d6d2957f95b2c6f64139a4e4368bf7ccea878ce9b1dca8e163393144d73893bcd1525a99bdacaec35750516e650ac39decaaf3b7463b29e8aa2279d8e04cf9d89bdc97f7e8641ffba0f3e09a5918e32575616ff04eaf5f5c5b2bd208f9c2fd6d574725712d0e88e020ea449d4de5c787ba68157e808ee74e0d67b8f0f8f9d95096677136d93e0a44e677b3858066990ec33fde2427210d26014ce83bece0b6c3f2f9111c22e0716accba6c0788b9f77f5490b62bc69bcb966ee63b3f52182109db101076877c898698368781cc36314398d470e149d73db3072dfff05e0c9c5c894986c12dcff7e766cc274561f2014596fb10ca6f2a744c6994e202bcefcbf3b7141c63bc6be6aaa34d83f1fa5a75fb00430871fbb36ab82408e058940a970724a01914db0fa1995097c47a32e06ed6661b1aa9fb2cbd132ba8560272bf6e63e285797ef88e03565288c95834d6b435cbc90b715f67817c9da783088d9c52458baf5848c0d0b10ae79803ef9be2306732227a7dd21bc04d0ac4b3a368bc720da71ae3b5b13829bdbed91803b63f6ddc67cae34d9db296b4b7a5d6eb36b64df2114f60d7d7170d840593bc919b4e76aa26fb7341ef59daf835de07dd820bd7edbbf1880b0a1649fea34a40ffa6d1abe1c6110fe3551bcc38c0938591385308ef4d4341537b43ca7ad9a8ba85b4b1b01753e9f637508f97344d074c8820abee335023e5455487447e0a0d9e11b081611041694ced5a78f025a266f55459d4d1023f258d6503788236bfc5db6474328c9af9351a0f1a3272a1f61822913a7f89c31c08d200708a4b32a00d0d9946e34b7c24282a3154ab6f0b281504fc461f219a80bb348ab4844ae45edc2efa2116d8fb9a28e8648fa9a3f946779221106a8c50e80dc89497314d4f7412e85beeb4a45db85bc755721b15aa1a84e979c77eb49ec886b1a20c639cc754792554bd02c33b684fc40f364eb7b217e0d79ce6b0db68e927a5efcb0fc5c6c35a4bb6d16595e8bad07bc1d2ae81d9979f4ecdd71a343434d085bf3ba3b8e95c1b2a52ce052dd828d02c1d357739766fa529c2467ced05c550c50365c7974a7fc41dae180871cb406a4bfb7ddb188975e284d3141a9610d0eecb8f36ebefa11528b271bd7639e39250e8974c1b5ca5deb0c0747907f0b849c59bc6ac789b802722c1a0891f0fc48b7dc90c13c6c0086fa7c502d5b1768293af080d123050c717607d15f3cbe0bdaed2e415063917b641f4039f3c49ed93a66d15fa2ce86ce5df31b6ecd04ea83b3164a19c1dbd23677d2b9d0ea87c5323904f9d9e96a066348d895f7e465d6427b6c96d1da901d6ef834ac1a7b56b7d4b0a236c96cb6374297d6356f6cda0018d3af736910fc970dacf80e94efa0923f55a14a51b419c48061d9866677bf8e8be93586bad35ed7c2da7244b2c48350e8e3cd1bae3cf2f705c2cd65c29f4bf5cc6e56d68fcc2ef061aa2894aab0da4b94b42826522dd70610839acbd3eee8ff80da7194f9e01580d21478e22f890cff8176f9bc41d04c704e41962597a7ec46e26b15cff6fede5a58e518dd0944f311730959a64850dfdd33cf624936f22371497cbdafeb1eb9c0c0e6bca34fdefe750d5ac26425f7348cc0a575f648542b4a33b5a09ed7c3bfb74a30195d7b8d5fc6dc36a804da096409b3341e3e89bc20247d07e6d3901cb0302b9754e96d44b8149c4ca0bbd9823c7524fe5f04e5209938dfe0053311b2dda620f3a7d2ad7486860d696ef732488faf8b43e4d241d228a7c8b7c1b5cf3197cb0dde996ae214261258864deed841dc68c078b5684bb9dbec8608901ea3e2558fa49c9a40cc3e8c7fb0153353af760564f250ae671c9187aab960ce6be379556ea5049085605402fca08bc733dcedd464b0d7c1720011e70da42f12914c2771210c5bbf9d7320e3733b9b4520001347c3f24fe9dcafc9635616f4944f34d26adb3fb4511ccae5bd2ee69c8764a129399ffde659debd84b287f55b49676469c070eeb11a4d6821c9d9048cf1acd5341272979b56c2b56a8e30295b44dc567cd106afb35f6c5a3db944b0b82a4760a8cb858d1150e5fe5500f0733c53de82ae4be54d2854210f58e18dc98f4061e09944d9c6d1114b3dcfb265da822187d87c85d2c60520962de3423e6452dcc7a4d86a7f884d5469af4d85b2287da2eb977ee869645d01e61d2c5eb9bf7e7df71ba20b422787a67a794de85861d58f60731cda9786f97b9ad4f348691a0f25b28560eefdbde3a9151930d124e27ffeb08e90c908d23651508806cfb1930502268863d7de89fa7fcac47a20e01e7e46d3a0b66972a7b305d176afb3ba66e5d54572d2c81f1c7bda643ba5e223ed4ca5903407ab2fbce04e9b9108f6eb64e8246b3bba4a9744d49753674167a0bc4655f7f8448fee68e1e320fb2e540a9960e720056b9c838e1d7be60c87ca3fde21827b4501e47bb061073390c8e949cf1ba706d545f93f82d33c488f1d90e6b20bdead77016dc7cfd6cecd1622da0a75162cf938f37a40f5a741f1704f43e0dbf1c62081d2fb24844f48947f09362265ba5d50bc17ffe4cd00840655015a8f9cf853099654ee2693a7b4efc22b87d89a89baf126bd1e1bd1a47e0df88f19afb533ff4527d53bfff7032cdf4a9eebba9b8ba44a67b717f97937f2da61090e9af5efcd49c4ebb13b2e69f3897c6189231294d90a829bfe5e0646f842ac2114ec59553921b30c28342503718d7de785db8f489621d6dccb2bebdd2f6d334fd8e1464a8d715554b5674f8b6b8c2c6f64c3223bb8ccaa6eb2bbdde85e92fdd3de4920eb0a046277556554bccb9fd8ab18334f0aa51f0ad5d67f1048c3d5152a378c07a92a02aebaef6e7f801b2ba20351353e29442fdf7c5091e92b4e8dc5e14b80c6dfab2239de15dda2b021b86d4aef2757d0aff0915ea6ad966b2d5081d6dc3345592b948092229e0dd0b847d48422414145eb743c24e17b182006a5325efa286eb179350cfe76754538a8c1abf521234c5c1e66ffb530f9fa1401a5d0d5e34dd2117be7a5add0c40eec2cdd9c6fa750327ef01a9c2db44cba01b38aa5079f69a87cc884f71f48a11a5d1d152f3e9cca4204825fa096006a69347a42ea79bd2cbc9c540673423b94826da5b90bd703a943ccd82a74ca35b3d1987caa2a992de4670fe7c8f40a9367029fda16c73a1435702b48f381d793ec838f14dd2d2fe5d7f455ec373af2c29fa1750e6539b9ba90ac80db9ce984e7d80a67bf8b796b394cc04eb430adacf5385c28e8c61ad9999326d8b51cc3456e65ba14e61bf1d5b38ae9886f2a305fb077485b1274ef4bd74bb74d5577164ef67994c6bc9de7250785d9ae6e9d14ee00274e564eef42597e84eb184180bf96d5cc89d616566dfdf32263624bb4a9c32f6aebc6c0ce1a60a7797dd147b3da172c57e6bf79f365bb4a5735a64a0b16addfac7930e83a6109c3ec25bfd571d6baf9ce4e882de1b6834e0a250ea54ed71ab8997c9b0ff7de5200e6f9a95aa186d0320a8908804b5128850d364a51e0f43660abd1067b3d88fb21e19ae859be2a593cd2aba4304c12a6874bcff317980d8ace3ef452c261c32aec561d2c5446771c3a124e2e513128c2055d34ecd70fdc2aa12f5900ef76036996b6adbec5df3085d3031952b2e71bed8b8099ab5e5a2f8c9d82aca276b42e3f170ea204be64ed424858b38170be276b51cc94f110136a2e9b8c0c025b64b88a959629297e9edd3a607fc891db1200b71cf7d070b164474b467a467efb4692525e4b83e913b38fb71a1d9c238be55522b03b90d807e5516b9517c1dbb3f3c7ff0d1f4cedd10c793eb8f9d58d761751d991d73ad1452db406b20a498e40452fd6e3e628eef65cd8956b1082d7f52bc22ab6d154b801ac622ae64e83b6ccf1a48f0cb0309a3c177e7170ea2197aa6793cbb61f68216b2ee54d2138e96081cbb2d70a1d5705ab1858180dc5686a949edc8758369b9e1938c03dd61e847549e891dad3b5150396103aa0306c8fc40e85882070524b21ec45a1b92e34260f165014cdc27d3838c0dbd686c8b9abacb7fe2fa11bc9297f23645629b2745c947d150bb90e953c27116f1e5f25ab69f8d3a8a01790f094c9023eb285ca3650c7fb2543f572a0187d93426095a1fd4568e7eb283569a8e9908deb29d4faf0c049e33c10947ff48dcd82a1d013daf4df802f644415c51578a3c6c85890cf2e304fac8a3e500ed526ac6f93748e4cf8e684116a34bcdfccd71dcacfa6230a3ec84b5eda2eef3439065716715692ffe41ccadeb7dae434048cbeef839ac20819c974c1f7b6518ff02640fe5ef59ce170cd814ffaeb013edf212f13549a8fefd12ccc7b3077c7fe34a365d29a7f3a5b5024c0769a2c17c45ecb6a4db0a0b2303db6e2cf5e12a384efd62a6a56f61c42a1cd5b44ddc0a321e34242bb969f718da17615dd82fce20d953a3fe7b462aa2d9f37367c0c857f55ba63e18da19b072df8ae22c3c5a75c1b177b5b5b70c4655ff16254bee8fb086194ccc6ad45192cee8988275a553cfb7e11b3f34164c488494797ece918108af92eeea00e5e49c2729c0e62baf0aef8e256cdc36be6308d9d257b3aa072525de74456b36f51533e808392b388665d55359c4f1092b7664fa3178d874e16a5d1db7e2a955d893d98a74130791f1125566ce2411235d35312271efc0859beb707acea4cc00f61cdf44afd6f5123c64c74174cea8db4c3e331122ead50be7a760ebc45e159c9ff47a604812b964463eeeeb1b5d547e927d09823341cea1a1c326afc979d1d8df3caff4828c7081857b2494ec8fd736b5ef8f392086e59d0d308acdcbf40bf66d2b8a15cc65b98c95641225817464a7b5c9826d6d914faded8492d5063390ca4ddb7546da924a49392de94583fdaa8e63e5e40786e79f5983ea115c760a2f206f33cfa090a2ed4be48c5287d7440cda31698f37fbbdf5cd0cbc7760d937582c7fbab00cb779ed343b7117d180393b42b18d60dd67e3a0d8d22044055175d08a6720a31600de996e65523f87ba338c7d0ce9c6e33cd2b039c5ea1bc0cb2655afc14961e9c487e80dd430c249d8004780b24f8c298b4a569998accaddc273a71be397a76b67e0085d47d4fc93fdcd723bbf16302c126d9a9d613d56d24058cccba434354ce583b8a703a68e94de0c519868245cb38916f2f223947c8558deb6ef4a22b5b5d4c0c9ec435625107de16e33202072388552a983048c7024868aafb29a9f72f5a33dee7870bcb530ea0899b088fb1ea1e4234a7e4aa9d39bafe2dc0d597391bf599025502dd2179f1aa0e0c1ba016161d3ac00d8a9db6c558e46a7c52c50b29f676e0f82f6f0c7a251ba563501cad7eccc85f16e103e1de41751fe9894add0277b95a8c686cdedbeff5bd1764c1a201560b3cd7eea42a5cd5220d29f5b098a3707433af149a8774c486a9546d80a00a936ef6abcadb9635f449a40fd918e28318ca0d4aef5e6b43363d5293a2b4bf8f2e801d5cd92ba70012bbc5dd4f49ea08e95aa20a42195985d1a03fa0a1d3a59fe0d603ed68108f4888e5aa60493e03bc9241a9919bc6b6ee2db59513ca3fb2cdaa7aa3460f6a6fb2b1b20818332c37fff9fced7a7158b4b58e444e2868a061e3164bb12df08b512bb29532f08d127712a697c41d81048de6b14ed50de69d3ade22dc6ed7ca08f067789ce009adb8800910062d17a7906decebca02e92b3bacd8331c5699df3b207dcb461c2e6dc6a911e650b383aeff3c3e46434af8b58da7b11dd722d1321f5857eb0323f9470bd5241e1036856b7ef8d64c7b00364113ec924486141f941aeb01819063707361d44810cb06c429f470fd460388dc5841a932901f703fe42dbd7585b791ea667285961203c1674b5481333694b3aed8442713ceea1306fa3166a3883cab6391c450fc971a9989d9262cfdcb9272d4fc76d812eab96c2a0f103db3c38ccaf08b7324c7962ea66a6a42dfdb138267684dfa15b2f111d4959a0dbd19cb4294696356e83dbd7c7923852dc3241e3108d3052b3997db1f27e18da771713782e52548eb825cf114918a515247309c45a1873d982f856e738fde46e45176500fda53f2a0da9b42c26d3ae03a012fd4cfb71b5d8198d46f65667e5d943cb59f4a4a651aee1e0335eef72f3ecb900937d488969c71b25cacf759f50172c8bb31ef815aaeb02cc32e2ff99a6017de40b708b67cf757d2491d296ca24967392e5a6148ccc5f273a0b19cf9e3ee51b6afaebb6f473105a59e0851be0daa5b83cb514ca96a263e877d85ad46c249851905efc1ca1293dc48c3b98ee8ec32171c429a3bf3bdfd5b1669dc315dca1e3837e841dad95aa9eaa7df8435f442b03586b6ba4bcfd2ae8c71c789857d5f6d1635ec0df8065c4335236ea1de9743f3c690a9482289a3140be95a9d6cbfa859c4c54e6910d86e69aa549b469a7a04145f4e5b5f6b52dd533406caa05ed1fad00a4fc3ed9cca03d75ca139f242be908090acef539c73b9c37c8f501f3d5707056bc2228d6b0025414770c3517a1bb96d6170355b7c72db92881b8f832f0554c9ce944e653d82ccf033643a23f62ac614bace8adb1ab7801c9ff3004f1ddc14dfb4e0f2e44297cef77359a11dc439bd1bffc8df0dc43114732a09b83eaf410aecd803ecb7104ee4c9060f8f0acd43497121531f0feeea4612b07d243904fab1712f600c7619129425e964540e20abf04aea8d5178f804928bc9dea17ccfab45cfa264d2e5774fbb06d39c1527fb9113cc6379eb5cf77e9ec82b429d86d55133a279ab750559120b8daf620e4c5818c90fc6d7a40344943afffc0b21e64321a701cb26bf8d949087b0ae64b8c66b40b7480493fb9cd50ce6f459e7a2b0b5b8092d7c9ebe799b01b5e5354637caf989286c319e4f3406419e7e860bd26d381c3b58bd7b0b91fb470b22b04d847038a0f998fa2d3b1c8481b56029a285fa3608dcbe67d97ac44eb9aec6ea3312840722fcc8e0283cedfb516a0e961b5f55d6833e86c0e3d481b58e2e285acdd853acb1361df6599cd823c7e5268a08be1d31a21e065125ee53e136799580285d0e0b644dfac21e7dfeb1c74dabf9a44270f7c7643906f03b756c8296d7a1f13f0af1d60411a8765ca2d5aab4908afddfbfb6fb6a44c916290bd8650b598eccdf8300ce6a326e53dcac5055d855c37b527df06fa0b66019d27a0bdcb0de90c8ab078b01d1da8233267a43a82689b18d2017ea9728bdd71a8ae1992660eadd470dd259449af9556bbdb088deb9f12a0f5a16095cbbf2d3c743f984b064ebf61d57aeef5614bccbe8d0d24461c20717755b9cab282245bf9e91550fd37d351c8ad5dda24e079f64cfeaafe7047e72b01273cdbd5e3348b996b06675b771933133e7932e3a7f9d4e963963659e9d3a03b127a593821d3095ff59cdf9286ab0dc290a43a8e6d9f70398b96e3883c9dc42ad353536ae057527e0da51194ef003305ebf79cce7a880129261ff7820dfeaf040bbb58cc6125eaf5a6a1d117a60641d92802f78111bc76fcdd38bf177fa06f0227b6987de01880d426d9f3c300e5754d10f83695239ca9800a05f826998575ca13e6b66fcc33a5909c9421fe53b449a8b5565f3551812c829f756f4e4f0b862cf5e4f331d8adc70cefd5a68394d9ceee7c32b3bd79124256e97c1b568b81c5ca7c4f2c756b10f8d93a1858c682aaca83cdd02884494986f29df4a9c19431b291e9fe9720ae07f361dc2cb9d952e0b6fb71146140c851593583e3830079f699e529a17486f0efeeaf614f620142ac761eb1bea9d7b5559796f0c49d4490669230c9e54d1c0ebd07070fc8d5b66dd900f1834ca759eec96001a519f6ac1bb93f7c546acd830b0c3ed80dc396b86f06175f254ed06f34ba91e33e45e3734806ea9f6904e19695d44d7bc7e19b195a1522b820fcfbdf8d2f2fcb29187363acf94e9e044fe3c5763ebeb8091a827beb9eb35742a333560b42e82c6a33de5218453430d9f63a50830e4a199e1c4ce9a7b3d7cf08d62a8e67f438833a83caf06aba484b88ed9dc9691e81d60d3760eee52ed36ce0e3b7aae00629f9050b43cb49ea9a76d748474a30121096b978a3628ae99117b34657cc89140b56ab1410a6390ef1a418647d270b997c2c07d68add38ab1f63b41c796390e3027494235b77d55397d2ff9fcd24381a09bc8783017a2d66c29d38e9e7b8de49a0ee3394d622614fa31254be82959dc180c42c6a7fead83954d10c29f25ab1b6d8e66269b4c4a5312ab7f4daa3f4064eb4b2712dc1ee1ecc0a54c8566205b97d74b66e56be280a55f21572c7eb5feb1322e87a8770b5d8955da799bcd19c3d9a37cc00155408b6a34eff03b6f4c753907a178ed079b5a2d102406c6a39b646bd3d5956fe1dd2e20fbf3d880664e34e747ac7ecdea8b6394fb983a678abbe4445a75b528c629e03d58ba29a7a29c03083a1f2522b54fa3afcf9ffdbaff2d30520f6d1efad40ee1fdcfce03eae80986fe9930aacce06947c6e782b90fb07ee9ee5a70b10f72bfd778552fd7ba0ade0eff3e249d78c193badd6124e7065f997582a45a8c78a1787602fc4065a3c4b25e9ec4450c43837f885f8d7d23de614dcc6cb82209194fbc551875b86408043b13798d7dab07af5bbc7ec0a200a4afe718310bb9a0596f8bb8f37818fff86b702727fe786e2f9aa6ca0f45bf7cfd2df3528fe3f3d88cb68091fa9c500e717f2a2fbc9edd89d3d778c3d4b34cd238375d7781596c7d7df902ad3081e697c5c408ac60ffa69ea8d5fc58337dcc196fa6158462dad3e4416a8f84341ad9a4f0df161e99c0658de7d74761d7e7c6ef6ab10d7df7d780b7efe6fba2b90cbc76d704325f53e3b751482d4d2d2984cf95dd24fd24dc98011e2fb455176b435975a0da7d542207ee0fcddde3408c2093fbe7b43c55c55382ed728c5f14e64e3b854e94387b901b0f826e440d7bcf3f95fb67493f8834ef8f22edbed705de81823782cd3cd1ba6108fc33f7b7ffae3d494d22133323b88fef12486918e24d6876118457aa07ade4b2ebb4ca7f657921ae8cedd99c558fbea822942fcd6f12c1c62a5ec2595bf952ed042d60899904a24cd761268b684f661c54d66204a57be035267ae90346753897595da9f99a9892ce38cfe0b00735738b68dcfc001eb0eec0ecf9b5e306d65aea85f7c8a0ce9e2f7e52b4eedace68f6475545c61414e6048b5c269d195395d60ebbab6b9dae3feac8ff2ee6c474aed23dab0fa1caf63be2c72efd41ca9ada4866fd61b9f8a69acf243e0e934cfa85f1c5a686ab69ee4ac42ef8ca8d6ead0cd36f1f7a990277007fa75876c5eda20e4378bd531b637a64ab181668e284fbbc5968a7fbf9b338eb09e04870e0de18b47556cd5e0fc686be731691623c16c0a799bd0e7e80cea2f1ebcf98547a59dae7b0c17c0b153050683874c28b9c41cf9a1e0ee0c2f12508ee25b326db42782989c7c4a152721adf66a5b657c415b24f75c4241e94a0c5410692a1c6dcb405b0003a82145d9a019dfa833ef933a30ea5e186ea731eaba18874ca4f3ae797cacb1a80f3e299a7e60018ee3ac5f648e601ebe779c45e782e57a7dd7285b36da72e349a702888ccd735886c562c67812255f30857dbea9c2a5f22d33427ffc614c4b9069a59e1fc304dca7e61109d097028c2250e18cdd3eb60a66f0f13e276e06597086a85e65d435d83554f164fc206f64c474eb553e8aa2ea3c4d00d475833a6c4cc27172bb6371c66ce542d3f401337b246f6163ae121a123340d6f48c33d62494a9f0521580fc58254bc0954c8f65aa2e5fa1b8f73b8080d5c56e0da1bca737f92f49d548afecfa762131e9a53e5b4c4e9a7617658eff51ec82a5d735fae5cbd4acc43f99d158080cc0199cc9ec0f55a1887c4227aa50661755991c2622374ab9cb064b864e01f47df9cb5aa8900480a46a188658e1d8c707733061eb866ddb32d54fb6d8107e7539f4f5891dfb57cdd6115518ed536700c77637813344f46a1744668eac839dbaeac33b9d1d2fa5910e7eb5d955d9835008f0b890b27a205158d1f5a7f40dd1a3fb82b95d455861b1a5fb1a6e6212a0c7ba23f7d6b411e1558f6ecb68858d6cdd06a5719d9c6012e62f4d58c4a621ce84eb584f990a46c374887c5562f8f09c006f6d099cd3539fc429bc9df40aca78c47d78fc28f1b599b1011dd508f108494f25daad317986a83717e30a644c1f32763ed822a6d2614cadb9c281bc0c8b9a7bd3db81eeff487b9eb3d604486a447b962fa20fe7fc315bbde280d0d36df8e79b67414bc4a3c37706484dd1a26e42708f58a6d4002dc474e8c35a4e3240bf8052219f1871a16fd41296c15d1a9bdb704c0b57ad4f44e3893846bb907653ca7e6d77ba685802b3bcacaca30dedd91c5726b2174ca3b4bb144eaa2f3a71975a826ea7cb349ecb0e4a3d9f64ca3de5d5cbafd33fd3431ee966c742830716cc31a53313d3d4a58ac2380ebafd83996a524c8085316ba3f98239e6c17ee690768bac2946754ff2baa875c34ce6999428dc937283151fb219602d592e2ad6f2a81e4c17e1bd7d5022d376b8a55318e6f783ee9f13872f254c812cf9382ae2702f4ff8c1d853b9e9d7511adb0214aded7184c3b98b2675427d28783cb66228b396f162f0c52f36a961dc4906755273efcc2806939cb6851ce893369a766bac57517c4906fa149c9a926f52f12e2a532cae92e8df99c9bb37aebe9add9474c4dfdc759c02a505eb91bd43a9e96eb0f8305c8a774a501a0ecbca1340160790982b75a8c77052b0ae63d34380e466296ac8bfebef40cd3126382fa634a34d0158c5f6ac52427128aec5e0c03520ccd15d753d91510cc9ea84719321a4db2fb422f3dc9b0b2e1ff409c99ab3fbc2e98007146aa4a75df3547e89aaf2eec18746abee48bce4f33faaa4af8813ac462d654a6cffe07e9b29d944aa338048e7fbdf5caabc9c1e3ee337b3e7764a88a7e15a764bb34d1551aa4903304e19c59df132195a5961ef18f702a2e1612748691e608cf5c9f7740745f5ef68947a64c42a6dbf01d7b61012cab4076f3623cfd7559d7538c5cc45d832d342bf2a50709fbf2fe3b3706793b4748c2871b2fb3a6ece3bae0cb6d92e74031ef75efddc740f4f5d0e5d7aaf4da28886c32a7097efa47456d4b29fbe3f8b37cba70f606e846767f1b7523a57e3fe4f763c741686753064df58e1fe4f7c3aa9d46601df28cfafa5789701d0095c9bcc5918078287b954eb711643c26c2d0de7c427bad7f26598073304d6f2a8dd7b6eaa5ecd9e53c07bf109e814cae3f3df92f7869d2480abad02d017bcbe666bef96badc544fc537672e7bf1c3320b9caab03d6514c095b4dab9e7bbd96df1b254287374ab10b6c7bc1e551938e6f1356ce36bc4c553b6e6daf81bde04067006942733204800988c5c04902d1fff3199e3a6c945aada6413a4f803bc975c398148361c8a222b8a5f0e671e95b4de7ebad098915dcad58719dd2fa4dce4395fc76d909c886decdf649b51f4561046211c33e30e57d07afb39d4dd197b15de9c613864bfdb1d2f20448b7c5eb67eba78e1f88cc4e05fe5d7eb8d08fb72ccd446a933a76f86bf9faa442d2a257d4ad834f6d1d9698e2c012ab63a2e70eaabca808d281771ae44a387d69f827801bea3d5ccaad38bfb09516b459cb7eb3934d14b02ac108ea52e09482f9eacd9a5e5a415652e7b8fe4e551b11507a0f7d7e4dc3e7ddd1e7571ec5083a26a0c33de48d4b8249e8d68449a9354741f76a00ba996d15aa09f8167243c502b1da7e024dd5682de0e49519ebe97471990f2a7717a28de966dbf57c96e4c463fd500dbc1e711ea605fa80afdf7215598d7b2050a90db25b57829357857dd42a449c20860fd8fdbf3159e828ce0f769356806a5a7adc6d169c69dd8ea6be7cc8103d080b4bc94953346dc804fc38438d65b45626fb4d825decdb5a9b232c5cdac23f957c37d524a4d3d616d8f2db6ad553f9e260c46acbc44b3a9ead65688da22e5261d54b5cf12412a4e0f52b7afb34d2c62618c9166227b88138fd06cf158e5a8002067cf1242a87acd9b1642c09981d3113abab179f7d6e76eb2adafe7cacc0ae544a4146b69a3c16836de598586d1eb4c56f95b930a459fbc6ce8272ef9aa8e3d9715506cf6cc6a809f0d532a34fc464f3a4310c30d5f33e6c74e5b2521a5f8599126037ed614380e7196c2cff2103c611342cf2b3d0bbfe644c8375793be0c0136732a34a784d096bdabe41ea8ce441b677c58913f8f8abe32977c826a95a58b90785bfcee7c241d3ff2cf4e7f1474a0638e5b8bab92c7022672622d76c9e01acc3a6762404e4175dc1fa873c7fb36312d48cee3118320614d1a9e5353a27cfcd889aa7f2a09d7e6f6d4fcb98bad99029f16ae006aa275c9a299a3322f9783ebf82512efb2f4f3d5e1d554537a641649e8e36e54be21c808f503c12822c6357ebc72a952119f6bd44cf798a61e223892c2a04ef2490a7e255d4b1c5ff1aa0d9ea4b43d4415367b5c3330b7a85aca9b29c0c79aea6668530d84ee5b6592e51572fed38c0a4eaf639687ed8ef55064ae4f781c5d459209ac7bdfca1d8b6cb6d448f751afa50d2452f9ea2710e9561d30ce9b3fa50053d015c77a6161bcd6750474890aa5b45d0c5b7cc53a2b50729c52e5be8e061c9d007ceeaace74757b4348d7a294089fa3fd11b4cd4f5d6882056375e46de3fd1b49ca295f5249566e26c5516861f2cf5e8307be7edfd7a00fa08a357b3b3833f8c72a415d39a4f313eb294cf2915aabe000e16bd0c25639239bfb2d8794d67994c3ae04466b48f8414d4b5d628949876497e5f07136109c4341f4e96c851672636df644895063acb001121eb4a0508cb4cc41cd6b7ea7d5cf261ba3fbbc701d046e0986e31326ea4c2aa270ef96f4e5ae20c79ce13b40bd5ee3a299777dfab31ff12144511e2ee1a88ce9acf00e3f8f4b8accbbc100c703f5fd2ee8f48deefeae8df3510aea9367d89395ab0040104c2d8c03246ee6c00e3533d7b158a07be9f67c7853e178ec381aff59127cb6c2d9363a96360d871ac0223616c3cc32ed32bfb21c35039081da9576c0b6b2b2cba26ff752edc3cee6797d756b5124224e839781883f2be515be043c2b9b55cf48bccd6aab43444e63ed796e483052ef422bdc323294ab88903d28894dc649e688d6ab7ca423ded7cdc98f2f87c4cea7593b9dfd2a858be029f13a537b16ec8878769aa191816f443a73f23b9cd7d616615ba68ba721764643a4f9c312a98d1140c8bbb80713a4ddc6e26e27bb8b87b3cfb59ad912aaad926a7408f54ae258bcb52fb3bd1282c003fb31057504015ead56c3a3f12217bcb68372d1a9e8a6094ba3d5692062f218f50a4da72ca470371b350ef9ec0e4ad51300e4a31b2137f015f23cf92270943fa12376fa03698088b5057b13fff74a3acfa8140529187a70ee0022a36b862b7dbf67632c150eab57a7752a61c1ef959bbf34677daf34b29407e2a0e5e7924d448a2bf7e6380c4de4c385484f630008c692556554ad6384e855bdd2c19b5f21cac66344c2a9a0979010df2358429cd3e0a996c7d752b79fc307f26028268ce1c69b41dd60fc546861393dd6e5244d462fb947e62f8d97f33c45ac9be40aae37bad84558339f330da5658a097df3a0d224afdf2809b0d959bbebba2e416f09635dfe908eff981a07eb460ea0899c16ca5c548cecb691d0729e6de47d4b0c4da9d2e2b63e5912c2dba8a032b76b8bdd39cef815927d7f8b4f9877d3efbf0445b21848b0775229d3fc23d4dda5cb07d9b25d6c23f371731088e324a8e650bb1301ff776b61fe1673ca991b6bb67906fcb7109bbe3cb56dc9bfb62f51177d6d904f7bb50bc5658c938f6115ec998e7108dcacb70622854ab700da5dfcaa71c4cdd6013bcfaa26919b2cb15f17d2132f0694865f1e5603225227ab7d5c6e1d4dd57a4167d541f176a296aa3b7a903e7575326a7e0467e776b7923aa9f434f7d0e176f5f93c89424da2735d3c83e578773a99db76956cbdf75c48bc99315b326f4e23a1fea8cbbcfbb20d6391909b937a0a6451d01027c4b9b46b37df7a24338260b70fb9a191b92799af25eb3c219338efe0304e6c4aa1015c42f6ab4baeb91ba96a0c5504a3386c857e823423c0d80b62eaa53354ab0ed02e4e6c0953125f98828c2f99fa551267ffb08d99c23294981af9f3cea57ee2a21a9eba5daa68b67b3f441b66f92e3b999e8c043ddbe1089982059a20bdf57ea8211644e8b79bf10753fbdbe252a8ed0f3373903a59b6131fed83f85cd7ea8866b68b6e46426fd98446c5d1039075b3cddebcdd967364a07c2d5d21c5f563b72020a94871b73360f567214d648106984eb3a711e387d9fceabae4809664fb5434f90a3db7501d5ef2605e0b0d19b06a9939eba90dcd4ca7066e1cad597205a3d4f057148d7a0245d18319d8a0fdc423417ae09ea682668010c6aaddfc6a6df0ae09e695929eab477716a87a1a51b3b1617c205884c21f0c5a87383f942c9a81db348b080bb831703384a9128a4be3a9bbe839135b7f1d86159e3548b029abed23c68c0730389d80647eb8b2a6dac5f4ad08bd4f6cf2384265d72d7dfaa68331a3abb244a771b2b0cf7e282e6bfc9223ebb5eb13ca0a288e4f6dae1a7782e42a9ec70d1db9429a7f95ba8a71240deb0ed9a82bfdde25a1251b8b267d482f86d1b6961fb07e310b187e01ace000756cbb51816e786a86521adf045cae247379396d9dd2c1fd31be4ced6fb0913cdc554b386daad1e740226c69cf789ae73e0d221351d8b401ede0895590bd3195f06f17af5b7dc428e6203b19775b65b6e230c5f19db4a2d9e4d62de00ca3399b5db6d883958ecd94a63d14295feeac378e5a3b6829ce3832fd8e51f852ed903ea8d34e779d57b22e4088344cef5c6e4f9ddc643c92488c1c1339394e5c27a29025175dc77a85b6f6dda4e54f7f6431c290844225112ba13e2742160f35e8c9c388486b533e9c82a41e5f9feaaf2ac222d85e7767dadaaec45b1082d50ea122675c4842d3fe96d01d2c7391cc9f98f96ea64fcf6c86b99721893b565a324e546cbad1742fc9cc502a5e187df815733ab74a9843ef264060dcaff0ac72bd610a4662b9f5b0024ac38f9deab2e703e59f1464a7dbe22bd94d0c0eca455aff34e93e884f2d4f78bebf8ea4523b808747448f4ac72b5126d3d4bd1678e0453fe0e8750b1751074cc150e212529c9156b7549738fb103510da79185910615e2194a39ac57323d772479d42bd82654c28a1a037f80038b07f4b207138bb52df434399333503d3d660479652aa9377126653048a2424a818cf709d12b7b577ee716a9d5077b0a88e144166eeafb6de200a9a6d14429ac54cd66c0d95fa9dda4d5563f09cce451e10180f963df0090fb449edf04c2c54c506cefeff8ccbb55eea60b39f5705d1de2c8f2f25f0b44cedef018f13de46e5cba96bd5e93a8824ca1144c64fe991eee156b36260954833ae90dee8df92fd27fb10b67a580ff7069b4f650095055585846ef4083e6c7fe001e290f5ba7970bba973824c3cb75d7834a7cb5021b1b03d75088adeb8cc1a42a3c19edcea0f7dec6ccf81a534f5be7f0e2860ea79e86c0267393fa56a5668d4e4ae89cdc59d5bf8a8650e3a70b970e83ddec1eb8e8d8d9bb4cdbd432e8d0e8104c4a28bd10a69b7e3d7ad273e4de71b0a24ac1a0a6004fad086e5009a41636e6e3ef740b0360a3f297b2bc9307eae71effdbfcde1d4ccedfd02e355e5904f0dac0a23bfb6efe4f90d3ae508538e7d4e4b687ed220413e6c3afa755353cec4639d65a106d291dbf40a9b986193f7278e0f15c644afc76307250ab5c4820c8c5ea290b9795fde5a5e5580651405e8b9238410dbfc28125634910c17b591c9a436f40e682a61be6520740bf423d6dccc5cf68ef84dde2cea9b9c6877eb14cc48587f23f747e160bdc7c49f512ae47a19232c35e168e4585d1217337f75c806f4c4f931151bdbfbf1e1950360fc3b2de747fbd758f0f2db79b94fe138b8bfbc053c4a35612a1f06527029b270c07d316fc5a510fcf8c750bf42450ed6f2ea7f406daca62910bed45547d3630838a2151e51f8d3a0e6837723a9f8931b7b48fec8c95b250e69074d0e31f0e0f36ccc34d2bb0170a67ecc699c266846917411cfc87f559fd3b574888c11465c607113e5206c69c9e8e8a68045b5af40f853405853a73a0d4da887d4561ece9284c56fe2d49fbc87d616692afb3b5201d881aa4f80945e57e416ac7a7ab537454b0454630beda85964b31757e8dacaf9d206562d63f4b12b30235648c00d0901d1b4be8d1224afd8ae4fd3a4eaea786d94b4914e5471e5b8ab838a6a770b12b4cf244ef85cee97c4235452db39acf1316473eb0581e9c55b94e2c511cd7c8d34d3a42fab648832bd7aff5b2bd423748a6c3485e8c3d0c461711d129dd9931046d1b57e78738012b3b0dc1bb8089654df7c079be7b4463efc076e32fc77adc93076da93958eebb49bb29f05730b1154d0be60eb52c8c095e6a9ea4209f542f5227cc2dd3fa96da26302dcdc634a9849fe51722f9f770455847fc8262834713aa6ce320f5c292ea441f1993a8aa5bfeff0f3c955592a2f5fe2bf8aa4009645706c21e12a249148f795ab20c97da0bc06b7c6266db9661506fd39d7ee27e7fac93f412195b71fe5545de081767b029ac1a3187ebbdb0c8a8574591b509d5f296b95ffaf902b3f95eb9c30ffae48cacfaa7198007fa7a4745692f3e13a3156a26dfe16d196baf3952f80623a794e7d34ef7bc8542687841163c347de87ff90e318b15dd944c079c1a8b0809030880f948947a7d66159a3b742f1b7309afaf0538d3c530090e7d10a609bcb543325ef35b201b97aee324a30bec1c527b5c0cda2ee9543d85fb97b543999254ee4b6348e007865b07a32a0ae13bf51f6793df88d5709fe6ded47d300d108bc9bd9ec234a40e37f31c7dc7f9fa76ef365ebd1bc8591af39cd7dc0cf5bd85d36557b97c6a8c15065fe6d0c307e41a2dd6555271410f1cc308706e60727e59c5c7595b2622780dfc02c2e950807e23e0fdfb632bf5e495958ec548bb106e9ed2d3831fc125c31e47ea3b534773ce4a472f7692399bb137aeb78215853841ca21a488796786c63e470e8666bb80cd53a42dd5fee8828c0cbebdeb8da5013ad8bc8cd0269ec2ec1a090786f7da5dec994d70096cb4d1c5997294a38cc8e83f987e17ff37bb138dacd1894fbfa7a54a4ec7f509a52d657e23f968675dfd71c9aac861828e604a9ae1e45c83a59f3384adf9dfefbfdf87813f8f1937fae1c9f7febe6f90ca9634b544e95300d7870270f0d69c31f3a19c5f4d156d99f4a8d5a5aa8867c2ebe80a6469932e16f55a83d94b4e12233c49a4a5d720d3a8fd6556bbc2d7da11143bd212c5ce5c8572b23d48fe21c7cfdcfe185f7213341ac27d29ce87edd81782f4bb20e847d0b53e7c5f98d060b1b3314ac81fbc90cda111d2cdfa80fc5510ca8df371718c73fb7fdcb2bb31f41faed36553b8f044188582031feed1713a831c1c3eaf89e3fa6cd941a7ecc69e8700d3aa48d48071efd360708adbed885784e6780a70ec418ecef7fae785b246fea3f0eafe144de3ca43c233f9004e1b1487920467caba4ce8f91dba0c5c325e12fa86ea31ce480c3e20f6eaabc18ec106b4e9d245398a9bf2676fa34f9374acb41430e4ea55e9311d49feee4c2ee01c4e598320aebb4a2eed5ee097da94bc8a2bdf3755d5d45ef5e23bef5e645d2833b2ca12f510193d0831e4654863809601a1ab65fda51cdaba697f34297ba57d6f816727e4862d4346e4de4a6d9ffff7bb34d293fd9a30b376d6e432e892b03a6797f1fcb5ce057d794795e1e344728342fd33350a191fb02fb475cca7a3008328de3c87fa09bd3826b8b03c334a50dd97ebd72b6e0674663f68a09ab742dba002ddeab618aaade4c602cd2d01a6e62d55c844939ff2e699031e2c7853eced91f507abf9cb589668532e8f2f92869043ba047f7cb6bff7e4e06207307b3c8ca200775ea7fe2b2af42f8b5728361fa972eb1329f549932108e2dc4ba138cadea9ee6994b35bf1ea63fa94cd40f8d0f5e5c2c700f0ac3f86f3b061b8a24654ba92f250ef1a1190a1d7c10cc2751a9aac1803bbfc4597c5c378ac9c8f688e6de00ae8462cf1ac8624ea0b3b30cb9ed0a9692e1ec6a02d8bb8ac0a83d6edfc2c08f50fb781354bd6435e71765fd517683ef0cecdfb674fb8ae2498056f288753e23baf3e3e2ae255699e25dc188dfd7809d198bf08e81ab200b9bb1c5b4bde3cae396eccf6180daf62d37c0a99d0e729859c31c0ad22999c9b5abc9b86a930dc446ccfd02d26c78db61249aad76126d3256723ebf601538464568f7f8150d4d202e75ce4afea0001f236a48ad77414d2bf368544277d1abf877a4f50ddc834d1813f41f2c98a15be14ed60f3863979b57553d4d6314fba928b4882a62735900d55664d36f09e9d5104ad8c30925a0547eeb76980797d1377d171a7a9d29bc4806c42678f1ccf7bb1c9beea2ecd06902efeab589da3a259dbab538b0757236e8568f212759c7429bbabdd58f5411ca74ede10ad1c999072fbdf0aa27d474fc314f1670d71c4f4bc0d51e29d3d233ee5428ae3a18fdf5da333d6440f1084c21430190f40aa2f9d6ff07e7787f5e1c154355c7ad09e3ca82f402bd97d871a3f525bfaba292cc9c5c434b0c4079c28d9d40ff0c43638a3b0348696a2ee33f5c91f66acb9a5600547a5ecea75f28d0710d384554b10706a0fce92ef6fa3907bd69e2875c9678084e800acfdbb5bb2e0a14edb59034ef6c89e7566a3e59e747c60b86a706400dafd733bfb852ee4857bcb1357f55fe4ed4d828717c6f79515f80e2a8619fa89b2d8f6e2a8c779628b2e13b2e281e014d6380254dcb518f799db0680ce46aef97debf4c8532a30147986204b9d57e79beda18999cce0628f35b0c551051c18b61d732566b3f848137b84bf24ae06b5501e2405f8bb9d9d243c87b64d991772dcb6b91db00dedf75d4708648301b11a4c55b3aec383b1f8abe6382017e84fb5665e65ea05de97d8ca19dbe5bac32ff30aa5f2e56430123ecdd3d7590f5e27518dc99b8e160d47479a27e8aea957132e2b097a68c3b207bc867afc193994f5933eb9cc86b4a1aff9999db753f8dd4c1639e7b05f7870fbc799999640bbbe1efda65447b9d7e19cc3f45950f43c8bbce261540f9489fa519e087930bf1359980e4d49f04f0331dbd720e3952dad72193f960d2fe4c8f1fc12f4f3ca55a92ef8694013deba59e98e2f7426e69108b9fecef7ec9d020c9c7ace89319eedbb40c20e6f59d99f0d84202e1bdddacec508cb1c82706e87557448d0762b16c4cda0b078428d0c92f5064aac81ef39d19ffa647310934b140996f396396219b28b60be4ab696123c5988a899e1f0b53ccfbe5ff3200f454d0fbb54994c800f245258bc6c2996abe5d118c271509aebd47f0655ea6477867bcbbd90148ac4d9ae55c0af1ef116fa135aa237c0f3d963ed723ac5dfc5efa2dab53484db4f470bcbeab14158cb484ce97fb2b4104558e56f8ecc0d6ddcbca1eb5ccb50c94a58524f90149f456608c617a4206be4ba05806ce364859adc1e6ca00e9ed7714660e92c4bbb92dcefa23ecafba752182d948322ce3bd522defa33a9eb742acd99fe824eccf30eb377a9d29584dbce68a520d73346208889489b94aac50b84b6862868ea05b0f89f9aca8e6690f6cc823789fa998b50cc1e6f9161659cfc7ead4b382ad3c9888158ff42562a586cef5ea58379795cde259ab5802f98c08660f51737308342c4de5f66b7a5650585d9cc2678124b92506f65a3c7221bba902214c1e449c494d7a8408df06cf1a00cb6e8a0c139947f595c41366eb08041cb43f8a048ad092229d46fc7f47bc3e02afc72f8e7cdeddecc21bd012e248c9753c427f6f8d8f585d2dea8f5f82a381f2159f3f74ae4e8b150d5f0d02084f1230d9613d606a1b9ad308ea42d1e3fef015956532a1280cbcf17c664651df81932d92018e7a5b48fd29f9e9502363d6ae2dc4d2949ac3a22192c266aeaf782e3cfe338713398b2fc4c31376c25637de2a4326e275724202021e2851c6240433d2e4818350ee4527d10e951127b811b06406f3635a0dd2beacc80a746b60fb41758f04a37ac0db7960d389d7e7817dae949ad03621339f0cc5819cd222d3c8cdff30746d4c1a3db5d095127f4eafe6ac633b06acf91ea76c0d3c46fdba23aa987a1fe03d8891fe9df4a0957f205bd63fc34373e60f23105c9a4f39651685368ee3ef89310bddcd1d18b687eded84e88514d2a0f3570ae949e688a304529695bfd5a7cbe6c8097e854edf8d72825064b4b169d76f2fcce2d312ce77a1da4f50ae3d43f98d01d944f9bc50a4cb1a18e869f51ec4e8ef20da27ebc11c279efab2a9c86467f4b6729b77ba69f0de92fe45bd721b7bd3de2f9a208cbacc46c26222144c68c2dbf5dcfd28b709ea89f9310957e96ec9c4f1ccb36f9649723035d602d3f09d0597073e2cbe1ef6f95c0383441b5996f3ef560d0e00a1c816c881de97b816fa6332f50bf3e757020c986f7e3bedea3776c19191e4c4aa5b5432fc96e0bb22c4d984a71ebf32999e139a1118238400b4b81f43fe583c03f763f8fb625b48d156d1f3a470353de907144d1120fff634c7ba6351dcbddf8b3a2c999736e43a4b1d92590462aa3509f306edca70b31eb6da0923fc7e3eb821978491361b9ae99b7fcafac48ab6aa0f833d5510cef8b07f1a3bac68af1b8f94ba5024ae578acea3ce92a957d376268bc48b89c54b9d3450e3a2fba5bc3ee6c649048c2a763ab4fbb1f08414e400e4bd149a09486d3b2d79dae31b3afc85b5d7381dd27de3ab89f2f3473d8e858bf42b79136c57d295d8113d8817ee92907e60ba051024f3e5c3da31fbad772861c8e83b8ae13787c40bc4678ab80f4d67b523adc9e8918786d2e3c0e190e52bc889c4682cc0531cfc9234b904317dd79a7fcc508dca66fdb0fa5df079c33c9efd3c33a17f9ae5d7f9f2f2cba9d72276276b7fc1920b9f79f322b692cf337a23667619c5c09ebf2c2189cd8d7560c5503a3d9b80c6f077d551e3604d4604bfd0c353511e66741c8770f30a43f16cf1a2f502aa94f7f9b837008d1b30d48b4d9cac1919b304447ca1124ac1066add7f2183ea208e628a84e265d3a90ed37e13689c7185514cfc6b1579c4bda6374273c66c913e5011b9a323ab54f681940fbe293bfb6aec812b496f49fe522f45480863e08c11fa9e507119c3531250aa0265d919b6c5fc4d6f49a3778a9802b2d0c1d8cf38a7d28a2c1bce81086ab0745829a5f2777617fae710644c5261f09705623b750036ba5de4f0faab002d66aade928276293c0ccc68989a41ef6d08bf994e8c1b88441ad8fee3810a0432de02936753830536ee655f135c9a878e0ca28f34e7136e1078f83580354e84e8f0054819290d249f06026549682045ee87e79f0e1d2e53edcb3b076b1ac0e65cde9604edb0b7aa49860145f16958059ea65354391bff2ecbfdb783b153409702172ad01124fe923b17da6bc0440b608cc2f2bbf62d1c05516b4b299ea2ce8b4366f0c76defda08b34381474dff0e3a5567059c7a770c238481d3bd90b382371149b9b333f4525d3a224dbd6cf2214cd036f3515b036a9a986c622088dafdda14fbc8b3b24fec80d95f3761c82fb520d281850b240a83ab478b140dd3c71832a10546fbd41c7f1b6a8581189a69d52f907c684cf3a8bc19757f2c3b02805ba2a46798931dd6a12f6e066dc258a9537fa90afa17bdf80ca34b9b48569dd007b0682001fd15d4db96a988bc869ae34fa45cb91d357a3ad71dfd00a3651f7274d4c22ab5830ce04513e74d60c32759233e165ffeefd56bbcc110414a973321d965f6124fea1551562366722b7b60c04106f73a6f886aa05dec5896db07ab03b4b724f06f1b0a137df6159e39169ab2d9888642b11aae07d7b7520a56e7773494cb3c2ed3c87a11216aeb71970176f40098b37b1689165e444664600b0e542ed26566804180d69bcb2916829bf3de4344f50c323d9dd7a6e33d685648c22e837b123e2e4cc63495292d078582e8a9e2db016087cbe5c304beedd177d17d74006e66bcd70301709225dd7ef8870547536609ee7be1e3055ee0f5144f1780028b0f7ac4fe7759f3d3bdcf5c0d7f90084e7bc0a713a5054d00ff3af45f947c8a29cf13861a752c0de8d8862ba49e63b91919a756a4e891c40f625d6970ea86a0e6d9539cfd6e2e490defd3660090e40a43039c31e6baab786bccde4fb1e87e6c4c32bd6203b3665242ad5fb3592b97eec15dd67cf0bb86acb9c352fe2a4f250afd36cba6d69ac55366739e92ea9fba4438856028165e412b3f7e2ea981dfff803947ffb053db628b90715473f6d2cb571f6753c29c3d39fabfd051da9cc7eda8594c1eee844781bd209c7873ec476c644206ac747b1cc9a765ab8a60f452629039e76d02bf2fc4ebe51d54489bd2d9050956ef57d908185d2908d20846513a7340da14cbad11434cd495ca0286c6ff348269ce3f109a60d9b04e451cf0581c6259c494269e17f1f45de7fca1c38251a6b85a2e62dcfb658bfb2486f48630dd9b5de6c98265800f8ccfb9da0b99be952c8a5b24949c535c6071c1c407932358ae6039c0c1038f1f3c4e3079c1e504cf010c0e98f8e0e282cb132c27d81ce0e080c50916178c9c0d8b9c0b4405b2686f3d42481db52f78d5201b701844613a8a7d32931fcb5151534eef630209c6d87b871a7004e325c9ce49729b31826d6c40fe7ff9e4725ee2a7ee5b7776978ae3f40779584c476dc8866043595d0a52a4e2206ade0f368e34cb285761200cfdb6d6072b9a5642405ce094637cde53a9e66cd0865b4506f3f3e547a2b959376c95433e469b01620dedda35c24c21d7ba5ea508a3f71cc7215571ee096ecf0c35ada114ad5283c3a248215ffdc9a461de9d346d4e446bbea633057976cf34ef251c0acd8dfa66d9a6c6914484b306a5caa145868a46ffc11d7850ed61af55c1644e65d96daa2fb1d1416fa2c69829dd324cf60ec479789f6508106cc054e2dc10c262c67923154487312c8a29a094fa14204c4eaddd1f1f7e1c83a667696346b6f07ae7769ba26f79b4357c1f80516eb880e87433aa29bc3f74aee50bbf6b59cde0ccdc34d14623dd2f4ffea8e25df8bfb38f52df0d0037c7c3ef0a0a27e7703dd1b8145c3b2259300f82a9f7d7512bef1d418efe877da86475f08876f45f56a7d76ad976e9502c8895b1aa17baf611bdb924946a2852cee316a237d7eb8996177d1cd042b47387e6753da35a2ce8f14ee2d8fa0ac092011cb702baf3b5625a30122b4a0723971be9d5d0240a5b780cd932c2ac6bf92b4da6cc95935bf080cf7e9112a554ec5977bd0b183db018168000d00108423169fc3879662efd382dd5cdb6e0337048977d0851843d1a21b19b902de5de524a29534a323906ec0546061d2371fbbdbe6342726eec261897d9a3c70829c7e9d3298decd1e77445656ee6665ef1b0b35252b4520284a195922e564aae582991b25242c49295929d222c5703d95bed3074df0e3858ed4003fb03f7f67e08dda13f6e52f9779420eb7ffdb01a1678c4f48f1e3ffa677c00c97eacbdaab57662d34e6bdfb559ff60abd28798103ae7912797fdc109ad6a59754ecbaad5b2bed65a2deba2564edb98664b41603fb5ecad46fa5a89a8d64ea0cb180d7c2d96424ca699206512f13021a41136b7920bb2f9f3efa8311af8b67c25b950ac665a3ab5524fd6f7c9400a964b6520858c3b9f6e33cd6142fac658dfe34f97a9f4d6974ca593a515ab9996529716ed5d12695aecb2b6565b6ba9dada62e5761078415a0f2ae0a12b6dcb14486528b558adb5561415ee897219b6e2a5247f11b3c595df719cc5950c5433b9ab06e85b12a06ffdabb5462bb1fa53ab6f69f3a946b90acfc847cd30955782fc4c632a1e271fc75159d6bd223e910114b1b9b52cc103342e3f2908b7be7da6cf97a9c5a62c117103d19d5af6a4e71e97997929f994fa4ccf0ca9569769a9b5d35ed8fc3a573e40d73ef5fb563e515c6a370e6a0e88ab3010d6622966e7cc48a3519651d942622bae7cc758b0bebeb498f6e69cb35e97b5b38cdbf1183c24809d18b7b6b3b765b0976afb6092b0f3413e01eadc47c33153c029e0144bb2a0a0477d2950ca0da9d0af3cb5eb6b3f4c05ecd60dd55cd69ca492593708dd77a77659b5525aed55eb9ccdd18fd8a3935a9afdaa5d6f69a7aa5d1f4495b686bec5980ad8bd365473f2232624eb13958c4abad1e6a4fda9e1e8e8c423565ca66e2dedd49c7c3027c8b2cb2faf7c7eb77f556b59f52deb7ab9ea75d1afb6d6ab5ed79cf5226157bdaca53f5c713b015cd77fadb55e5fed556bb5ba3edfae97252db7f8a03612197c082b93dace975ba4802d5332a1db9570bbfb2b90a3aa5f21f42ab72b21838f1dc2d1f59628d9bbfc1d85eef21dc2e812f265d1e9524619e3652d7e8d0418751ff008bda90f279511993fcbe994c618298d91da47fb32a3d2b0b70b2cf5647c1f165684ae7f0af3fac6e3b4c30499ff842d16532ae9cb289cdfd118bb931f25744aa3437f59a1befcfaf2a5dbed4505f9324e7ebe135b8184cb5bf7815a6bac96616335f0c1f057be2c7477777e9fecfeaecbaf3ebf32f6c60e55fa5b2b0991c5ccbc093d503bf45c7f1e4ec4ed805c3fe554ffd7a3cef47ba731c4ed4cb8252132ae7f402874e19fa87d4136786d4709b20eb63fd8dc1b40f6be83fd609484c8388ebf58e9d0733be6d6d4773d06180f653a97df7631f262b523044d8840c61e7968e2be150f45dc8ef3c199d049a0dcb74ae2731170df2a872c97b11a095d9bdf4ee8476d721c989bd30c8e2c7ee701cf365854af942b2ecdad5fe1d79a83bd6bb58686ad6b4ceb4670b1ae0323b8f519c20dfb2ee7520df0e601bef4210b34d8d65def0fe35bada3ab1cac5cf8d7d78ed2bf84d00fd297b5ad7228e3c2a718a6a94073afa775db004d478550ae5bd758573bfaae011968eeb56d80e65e7f6d45aeaf81faa50f5b013eb5349e5d44e3db074868a67a3b46c624e748d01234f0a2872b6e24c969c11b4608a3891b48c1828d1e2cba499669e182087413c714518cb16365082f5e0ebaf0c167c5041b33382284141ef8c2a1d99358e9418828528a306309307eb0050e8460060f76e8c20d0fe2676a01041f95840813e4c04b8240520123acb028428d2f76c288e18da138beb8628d2f7000628915943b678a1b425e406103266ee0840c463882834f1223c860e24c94e04055b483b85f03a1c10a4cb005142c68e3ca93a0238048a24a193a51dc042b121cd1c139870eea98477dc83182071c84aa10f2a28b24c830e30992369a70e1460f3fb861a7c83a546bd1cfd9a802872da41842117a70040653dc604191143758a2c3920bdb70028e28dc1842173688f2d8c092640d2a3a597091c579806049c9ba2c48e370ddb378af0666e420082b48c1aa8d23de0bbea00284358aae008216eec3183d700e5d45f3674240838d31ac948103142af8289731400e35108309d00ea808c27b804cc347ba0aae9b9fa25a06ae3d89949a67a89899f7e23663a150747b2ff5527193728b7929b681234b725f501842976f7c1a55416148099a39dc1e84f320e4f1bfe5fbeca5cb20ce31c61863646e6ec76a38061912bd7b0688b7bbbb07dded5f0cc1848dcb649de2df84943865ed23421e4205fd195771fb574320713b2e3e31d4444d744697d1437d4613355119b5d687bd07df8bcf1cb93b135a05057009ef63b884a700139ae37e139ae3081f6a8ff6833020bd785dd7459734b91de9ba05af6b5e8f52f9f46b7c698ce05a42aeed6505f9d76cc3866b3d6541fef5a7fe716d317b945ed7a3924a2bc2eb9af3a2c15f54a01ddd3a3ed17fd7b5751fb85146e8d64fc94429a5949a34243ff5f4a5d3e7f0aa92f554d6497b2a27a62f693baf2afda9b4edbccab4719eb2140af928056a7930086bdf5a6aa975999686e241eb534cc8112c6e345131a98e6bbf926c36a9d5ac8e6b2ba554bec97332632c4ccd6ab56a25253204dc378338945c93860055497baa92265f48c651287abed55ebd2cafae8401affb56576c705dbb71fc1896828e1574582c6b69399dec6c723b0840cfb1ada307404032f8a1c10454f881c14bbdfb5e8a2eb1c20216acb2c8b2caa207484896442291acc63537d95e175643a21629c348d6c4386b49b6629624bfc79459e70d3eb65a1ae9d95a4b237d6335585071a5de8705155ad46b37ee7b7c369a265309c346d6d649c2b8ed9c734a497f6072bb1262762821513c1d45835fac83965aeba213c35e54e83ab517f9756b01cade4bc955a331be22dcebe5db4c4a292be9bab2cf306bc9397afaa3a74fe7244d3aaf524ff6ee5b3121ba7db35373d19a8ea5a043c7e8471fa4477f7a417a345ba864a8bf2c9494f84fca97927e7da1f24d4aebbbe47bf44dfa73a31afd57af69d129e3a8497d33bb8f3e6e2ad785d99fd6c65cf62d2dc604cbc565e33c25ff4aabc53457dbd6ab5e3d1ecd8b188661580d56e7b47f594a29a5965eb606fb4b73e2f2d3053b5d9790ea588d55ea51a273e75bef41d43ab25523bda539c9f134c7bb68487e4ed9d7911d6135a7ec9465d97dab1b84701fe6c44508662dcd47732e1aab7268319e92921f6397afd4665a4c73b27227cd8714197fc71171c30fb000ac20b3be9230dbd97d58ec307aa3aea32e04776e319ca5f9e05a32cb642a61d8e9c26a2c4ba12559d6b22c0bc39e642d914249dd43c9122450b2582154c6edde16a7cc9eb21315d69e32fba815641d449d2015f2c115a420293f353691eeef4e2d7777771a4660cdee28a3131610cfa4ddddfd6d73eda7271c92b1ea01c8079fc0d88894dde08cb62bf4a113ec27f6384e29fb14455aab946bbc22ce82d5b43cc1022f0769f65ba21445c95ed18cdf2245fbc04260ae0b733ea2c79830ada011e97b64415008a2d49acc69a431bf9a6680cc44c6345e794a325173fd50e3556683611b073111a7d17203cf45358a07ead144fa74be8c0ce18b8c1c297db9e2bf58f15f6664969429a492ca8790a70b67a722e4a496466843e40959d68cb2868bf0ac06d2ea91a34525a5fc024ef5cfe00917507270fb0a76c18309e29e20a755e876edc36c9eda0558633a64cbe476a4db98101d3a1c1e2108722e33ea0c7005701fe9cdff213f3ed462841a0c30e03819fd68bbc1d98aae1449abe424ebb8c97d451d43e11e3fc16dd55bf150e572143e217ef59b2b33464a235df120b935c618638c31c618638c31c618638c91c618638c31c618bfce18638c5f2f6ec29ad4fc71f3715c66bed4e4218174e7d6e96cfc643ead1b3731b9c98c52e31aebc60f86bed43a5edeb8097e4d9ae326cdf5331406ea1f1d37e126efae7a58b1cec331e24fa43484181d297f8414638e2ff5b226a54cb1c8cc94eddbb7f3d219b22fdff25b76f61e43a8f379acdc5803318fa8138ffe01e17c54733caa64f3c43d6c02acb2db47e94ca29c218454ba33f3a4f2dde7649e52b2c3c161a3efec256d33c60761842e8ee2512917e1d35c91c645c48d7be01359146366272b72ed0ebf8607d30f3b6adf23c23a82580a4f0cc83132f718b56dce3211cc75dc705c868fe0293c4edc56e2f210dcf360fab1cc50431ddda0733bb6e29dd2f7bc8387ac8718430000f009ad739f9fc870f618e35329f6a2e55c8694b56c319eea98162a99d74a3523cde9c4b8cc4d4cc7c6b00dc75345dcb84c0838d57fe4b60e3f9486789782e0c134ce83e9ef138a52ea7e55684d9631faeb60b901d6e485c98bb6d1ca89d5c09f3d847e850a72c325346b50ce2037f40aba59469ad3c17945416ea8d0ea4ab551d29ff73d00b870b7dfe1e7e8b60610c19379819b04b7df5ff8ff87d19be175ab654b6764fed9a5a13cd5ae0969b957c589f6d5c8416264ef7bb4174c2c71bb179406125af800f14616ef5bf9f05c9f9d2134ac30e28d2b2b24a8fcec80c4135c64f3c9946ca2204691b9dcb74242091748ec5cfe2148e848aaf1e85473533214221064483f9a09b13a420a8e49680163e4e108373e0af12360e3f15f8814750493abddeec88d03238ab86f7504122dee5b19d1c48d41866849f7fd08bbfe5646bc71ed7d6f5f66971f6a1ef0f60bfbfabf77026fcc84878b81d2ac51431ef3f8300ff7d0a6fe84c03750de6238f1309418ac70e07375dcb7c201941b7970e5773eeefbd4fbd1438f93ff725e28f054f452612fff659e927f59dbcf069cd47762ffa95e6c58657d8eaacb6cea57fa3a2f44ae9f7ffddc5e5a60d57c56cdbf7ae8d1047930f2339a192e41c2ef7ac899f48f6e063a4fffe82b05c0d34469b40c1287d2c395df59fac76b1924a395194b5cf9aed33f90c47165e9ca7725fde3ad7890c695ef3d1de15b9591e5ca2fae8431fde3adae1841008ed5e8bc10d95e5a50d11fd95c4fb51cd5686b21ed253eab5e88d8af6fb7339edc1ab69ff897d65726d0aa19a2ac8b747684ae0cb94c0e1116f95e448425eec887453a573ebc32047bdca1cf95df433c2ec32af9fe44473edc81e23d2ef3544fa00005ada01016a22ba594d281ae945fffceb8cc4b0bd66e2ff09fcafe53719e92b28786784ace784acab733778666c6652e99aab1ca6a4dd443d635746daee329a915794a36164fc9bfb626f2945455deaced3ddde4cfc8c36d5fc0d1377e227be20c25cbedec7da97702bf12de33bf08f1fb68e4738780cb5f85e75fd9c110bad0e20d37a0e82054c40a8adc2001144e4c21a3066976fa2266863f66e6b920376a91eed1f58dcbccb7b61c8e631dfaf1f9862773f358e7d1f88dec831a225b52821b1f03f1291a991a376ef1ebcbfb47a1fedeb8cccb8bffa2cb91ccda5e88bc10a1fff3a505ebb716ea5b6f83f5f56da8db8b8b0fd9fcb9d97075333a1460d57c1fad87793c15ffda7827c786753c15f95378b708eac1585a8ca79ea75e88bc8dcef87bf1bdf85eecd6e99e3d91e7d1349607337ffe0cbaf3df0fbfe4a85e6c787c8e2a00ffbcb1dcf9530edd2989ee9458ee9c3a3e3d77f2dcb973e79cad0d612c77f6dce93ea2818f203fc77439994a2d196984d97a7900b4e8e3a9f900d0228fa7e6f340bd683b96c89eeae5519e9aff43a47fc7efd88cfc10d9f12f9b11eaa9f93bb68e3c733bd26fe4670393e83ae96dc7f78fc0014882543b7a7b21b2e3fb77e8d8b8e6e6c7786a8eb0c8fe469f27f323cffcd8337f8716753c359b8828eb22cf9d1f9f4001d2d9e171991cd57cd9237de6cb273f7b7c26d07c82dabab933e56a3e095b47736c9d14baf3234f90cbb06a7ebc0265be041a72d93a79e5b47572c8b47592a8b475124bcbd6c9a26ceba60e69ebdc67b475de836d9df3d8adfbba75a8ab89acad934177d2ad8b4477fef32874e7dc3430834c9da580fbc1c8fff773a3d6b5b4563d3ea11fcdbb32bb925596f618aa39f92e4bb2775f6cf85b2edff9339fa38ae13c2525ca53f28374098cace5c1c0e79f1fb9fb0d4a451cf7ada848e37610b8fccfca7d2b2a84b8ab2a40b7cb6e152897ffc45f291359975dfe812c68b7bf36f6465470dc773fdf9c2fbe47d84c1ed6f4159d61051a65406902860e46d4206308546861058c14a8f8337e1484ffde0fc8d580082c51601005119c34000e121cf941892639e082ad48c30452a2007164e706191861e880890aaa6823057c0ab2ef999f63a7590c52608327569800428718a84e3d644f2a518695c6751d2fbc94ff10d6e994bf6f3a0fc6bff486ce0e8f35fd2dbf4ab79bd7a7051e8c7f18fe61d0b9bdc0b9cc0574c8ad83c00b23bbee480ba75580b8028c2360c1c5132a97c016514820b4451c53de50f9b30eeffc60bbdf56846f67efdfaee9d217d985a3b90549ac0d0a11d871dfea4a19ab219cd8a9efd160efd97abdf7ac6e2c85fe27e46dfc60defbb7fbe329237ccf36f7badfbb9b2d19fcc053a6e822380dfab397c50919450d9e18a33f63a0272414dc44fc49f213d44ab41950382175fa332b3f7ea479782292d19f71e1447bf1f30413d963230c66c2096b5e31cefcddcbe2f6a381cf8f6a7930ccf70d9954cacf5131e471a099eeeeeeeeeeeeee1ef29eeeeeeeeeeeeeeeeeeeeeeee6ed6d54fe940fe6785b301ea58ce2cc53ef99732edf7fff5e0c1697b72e8bb9ef87bcf7de07793fe47dccfb99172411c61943675479af36df2ef3eb6bf373c111605cbeb408fce9b7cbbe3e8fe79af33fadc054ae90d145b9bc129c84eb72cb6c94ccff65d6839ec09884ff9020fe439efcd7dcdb7a6b521719f5f7d13f8698ee5bf95cb9a42d329fa0eb1e634510ff21fe31332ed778909bc9548ca593549f99b1b971196afd3569111a8ad4406bc82e6fef400c54679c815a0368b804203e1304487333b186d8f3a79d61c653fe6f65b0f998f633a24fd2481a66431ffadb6ffb238d55d8fc200d350f500f6497becf00e9546ade6ee6eea851da7edbf4bc5befdc87b930e765d1f96fe329183c882667b81e331f74e5785b4004b98f4aa6f98f46f2e3b8cce95dbefab0ce8e8e91e63cda48894e4a6f538282e3297f5235955c70d14a3c7aecb8fe39665113443d847c9a40993fba2714830c596c5c060810204240804c970703b7d383f153e9d5bf34d671dea975c3f114ce9c917e07726ef6f56b49bb5e6a380f0604fe3cf5d2c2f5a6376d363c551d694f1521abb24cb3980d4f8f8fe340170ac29e906d31c9d452c23293c9a5c5f4279bd2bbb005ff6193213364683d9892dca8bb6826530b7462e236bda5ed642ad376e3a99b2bf3e57c295f4ecd0609f6a54f8036c35a10ebc7f5570033c0e516f360a4b469913c5ab41b9d1e72fbd71c6dcea6e54d9a0d2ddff49636232dd6975c68f94ad29e8a7e562f92f65417ab46368a48aaa4d36572a9246b492e1ae94d1aab4e2eda75d27e4cb392dc1d420821740923162bc93d929e6ada8c648bdb9f31b6688ce5c4bea4805e4f2d271737c11c0f4978a8537a984108a164229d9cde3527f5fde495a455975325413b7d757b82104619a194104208218410c2971142081f4208218451c293cbf48d3e18787a3eb99cde6d307d7572fd09c2ebeb05e15f5332189fc6a7b2bf5b703654a7ef40cef5b719fddc6e485fbf9e4e7fc5096f5ae605860cc0976106f83846205c9de173e1f34effe0993106be092e5c7fda8c98b61722a4ff4b25ad2fb98a55598455c3f1147c8b6930780a4208635b336450743a41a226ae3f1a715cdf6ae8f77246ffe9fe4cb527dde3c7ad83db66e9e5dff9b171ce55ce47cb54994bfb6b83cb5fdb4bbf4af387c5e548e6e2323948787fe6e11e9cca828c27d7c40c3173f38185e34c4f1e60ea50f649d0aecfa13dd549fba140c9051234d357b75cea00ecac808ee64e2b594dcd83f167ad48dfc66c2a0aef0a1234d3730e8d55271a5e733d0396191e16085f11229e45657a591223839a0d92d1d7679d9d191e4de33c1ada53431b6935b2beddc37263fad33366e3426f5cb4194e30bc006d4ab439cfa2bad85099361b4a7fbd1a99bb3037f85451abda5359dfa2613fd29eeaa9e4a5aa8fa461aff2e71d1ec6341c4f79eaa96688f1918423e5c8b66a20818f2407aae2cf215395fe65d897def4ef51ddbb72e332a7f79fc1655cde1fc765489fbd3f0c2ed3f28fe80686172293e8a4f45073727d6b456a98a78e34fa2d9a7d17cd7a527d93f64381a72afd89f459a31e2fa01b5dff8b19e05f2b50d6ff30f91265f710f252946d1381fec15fdc70635c8647370f1e97475b0f066eb44ad6dfa53f69ac32cd8d886751951e56a08c6bce89f5576b3138d9a12aeb4bda4ea6b2181332bad7948cbb5244d2207f6e29bc1b7f478d92cd9f1b77e36ca84aff3024d9f61bca53ad8a1a05b4a7825a0bce86cac2214787beadd75b2a9f0147631d1834a21c9d9b99514eeceb494541e60e24a250a4c626c2911757924c8deaeeeeeeeeeeefbdf3df76efee76d6f8ddddddddddbba5107a34f7982d67dd9d41871bf514aa75b26eefe641adbda4bb286f74db1fa473c00761d6cf7049c68f5a02e1f67220771c34d04942b8bbbba98d8cdba017d51c7477e61940ab47fff038af199f76d4e2b3a4940da594705e301eb9a1efad363ae7a4314618638cd0fd488cf127b56cb56bd4359ca995c211e4c52ff011993da824cd1894390915d1080000005315000020100a86032271583016cb92aa3b14000e76944a68489d4be45990e3380842c6184308210010008c3164866466e80416ba773cca8a910a123aed8099d7a6a1a1f5e9a2caefd10547f0c53326bd9669d177abd12e58a1805aee16baee6834cd8899a9867547602566b7cbba77610a4aad3b35739f25ef1d6e1704d4d8bc3bf25b630f935aa8ace3d97270193b97aea051b3f15b68ca0443558f0d372d04d98b5b792cd8d0c2a4ee7e8d040029dce87fbd8e25be6a149c76cf1ad0337708caf22065f8ec83d5aa0d0bc3191cbb220c180057d7f14f4442cc3ca0d25afb469a23d1585a2a87997dc0734985563960cfeb3fe591aa931ab640487d1d73644b0d5fa52e18ca18308275b91afdf9a7761b4bbdd389008ca78b30a2cd4f0cf159b85392365012bb9a42a023c7273a2cf1b54c37b365e0f2e05698c40cb188ebe93eb1ecf689e44a05b0850a85082ff0829900b89b3acf18a51a44679470d7cd2e43568c9e04e2f48f33fc81398105e9b45ceaa384e5a6091b4c03ace77f8a60fc0b00cdcdd0eebdf000c9e6f7c152c8c799bd3b925e1d9ebec947f1874511fa5d894b0951546bef1b03b00a6db722a8828221bf0dbed43aec3630c0b1a081d60e4419f7e4d28197a936b9d1757fb3f65bb2100b02e410f9674e2ea13c378c8afbd2095d6f4ce583e8697b93c6967eac24a5ec1cc328406df7c4be6598ca762db3c408279430a1001b9793bc4f9ce80903c01cd15de3d0772248866c2a63237b1479445a27c52137436e345d332d9ae4087c5fe558acfec97b2281c2d184dd4a23e642a2ff80538cd9bc5f747b338cd264070461ffe26d2bc7f2fa37dab58f82d6a440efd48be09881eec2a30fe701d1bc5518195051bb9ce5782db825b06704717d80cf690a43b28f9c96d74fe11046abb34734684da6d49035af9f23a86c5c57b14b39a10cc48c22110ae8cbafaecc5dc7612a27a801d847373f54618d582a59426b4914db3ff51b8e110ffa63e766be5522b8bad35a0e1f5635c700175742884aa1203ac54474c2bc5625b055a8130affc319670a68f1d456640fcaeceeda9641322002a88dc38808f147f860170485e11cd7d7b82a89d5a14f3835dbc9e24eeecfd7f530513a62d2a885287488b2b0924af2d4b6e747a87dade6af888b79adb54e790864dcc2cd486d645760d4091f9836188b94fd5caeb7749fcf0c59fcd3b8e5dbae1435b3f7ebdb5b96804c61faa53e7955d706f3dc6c689d704ba3b715c81919b0b70d68b1651a2a3b2a6f933dc28efee651c11825ea34e0821b70d8aaa2aeee67891d8004598ce64db92a119bdc60b2e8d8d6214c9c926af243af52fb89d951bbcb7dc547bc455c5b6f22165d28f9ad07e0682b4de4a623f7202c0a60cc78135d49e911c65bec8b3fddf9074c9247eec07b4cc2eb23081bce0d0750ffbad10e8eb370d4dc35c7c384e8402c9200dc09f6d83e08914551140f99f0ed8cbf9343bcf40e57008c8cd102d4d2f1df465c784a93b8b45ee6ca4302400da97f8f17a1bdb18508ef4e101e1ca3eb40def398a710d4b32747104d4bde8669c5a93296f0b6ad6c9c6760b500981426154b22a35aec97da1f547631a0af68071e4d89809de46b767a367785fbfcf5df51702131a19b6652585b7a53284381c0131a948b1e6e0414bd61b1d2c908991f40c39f6018092510e8630e1be59075c5bc28490a083c34b20f9889bc0201b471188c20d1ec169d00d402c4cd2fb32d00fb576b398e130d2f7dad567ae110a03a3f61cede3d410b3a7a6d202a389f5f761045d07ec075b3dc74e4051ecf5f7b054d5e1decb2e500febb842fa14d387f6392dc671c8ef332e526cc188cb3491cf5ffbede9900b9f0dd6be0c14fc86a377786c077836748dcc4b2108aaa6fee64420bd760b02dc1e0dfbf0bd95ba1c8ed241035b82a1915d7e384fa8244a541e5ed537c2858af95fe28dcc2f49c69179d33bc7548338957d174c8753f99486b88974691aa06843a620df8fa6e2d1ac993fe9c9c4f4b701b0eb391f1ca74d64207c3c0cdc69febd03e2c7e9fd6a9dba2227eacb838a3624a36e9c073f7aa04166fb2d1449ff2333a53fcb975ea967dbda06576db8bd0c9e067c9cb398f04e88618a61bcf4e45e7f88d8e731a6decca7878d79fc7f40bbaac4ba785becd7d370d42d8633e8e65c1bab505bf591f7a1960fb2f5370e763467858e788c76a98cf19b51e7a76a176a0e54d7690a310bfd5851c200a31ea1da53bff9171aa2700f6a3a3d045d83609746e7543eee30e4577a6f0b7520c556d6b4f447bab83c5fd33442e8e36b54878cc37b2b33852ca7acccaf2bba73952da46d6e1fc32b0f1be9be680fc2b8d919893115d9c3e19dc286c995a252142311cd489d33aa29e218f7fe9b372467d4ee0c56695d2e28d113b690a62277253ca9a14980c735e1f55c0a4f8093549bffd04007dd5c50649b3482f78aff29de3337ca21a42e58ea632084a38ef05c5e325b9fc44455bb4934dc8ecf337b441f3e798cdafc447cd2b4887943013e51a2ef35ee42fc40ab86820b1605b1526234856d6172c7751cccbd1e0886094cb0e6458c69ba23900c02b955fd924eb6bbd4c7d035d6e61bef29434e2521f592de80c5d6c2fbb598a65819a0e63e3f0f8257d8e833250140ac84c70d7970f1ba5180e407352eec9102077d182fabee432462242217fef2ff3cc16b1667d0bd7b6fe51994641b367ab7b8bb0b2e7dd385866b473cadd2f0f60ae3d173a86c92767aeea75f2014bd9171893fbeb25c8c8ffad805b73153cf4a8e3ff17d5d9935d7a670c51e9a65b274c85cb5bd31ccde6a0c2de1cb5f839401e423a3a28cae88cf0928ae365d699d4a5f10611733a68adb7dd968ba559e1474b0c1fdabeb8d294fb491e6f7f9101ec8570f85a20de01b88e0da93443e40a2f68e95ab42e5e031805926eceed0f02e6ceb9e3df9afa2113bde87dd3db04c567ab26fe5b3351ba4e79a59f684fd4da3c860e93b73b2efff395ec3539ac6b9fd9f51040490a5cba7bb2bd04f800f29ee6f702867842bc46cd4f7529a4c684eb3504bcfe149f049dbcda0794d72142d624d1d9b57b7b7d840e3e0c537e58791dd73dc4211aaf193e7d8f6f2fde4ce0bc6ecb952704fac66a8837aea4482a6f069fc27007cebd6e5de09d6b8e949b96a6301616fee161ca53d353b0fbeab6c7cdcd3eb167edf4c3904a5376bd16acee134661afb20f1c08f9d99f69d61ebf68d0b005684106a867b903839a4b75b856118ab3e2feede2b6f3f8acd4d502f17a834db098533890739d05d027251486f88cf8b0f106a890a0b14f7f20809f28b9c289eea8f454071498cb5aaa959fd62b7fed2cab5a22b8cd39a11a3ce88b582168c21b0776db6f5f6ebe45a2397480870964b29e9050c470825f7f81c35644ed889a85338454e492d039adc08ce2ecacf2edee9d05c4bd20ee7a0f3b4c4a888bd1b2b7c48edcd6f3d98fb23fc545aa90169dad8b6ce74556faca603a9755e15413aac561ccd5cdc1f577dcc0fe5938a3eb209593e0e4349c7d19187fa21962c785daa1c3b0b946be88f3444e6ab8e55669e1671753e09616ce70f82ced180471984458b05a1762932fd4af3a0a56dc56acdab32e8f7358a518e71861ef9133535fa1e1ce7815975bd204e42803be09c4aaaeb919e45552a92977d79b7a816102c3c822c8aaa8419ebe4760d52f1c6a997240b9a14950155386cb16456c586b0503f12891bec6b59f8a61782dfbde988f1cbe88a1dd4e01adddd82e31d49ea8a28be65b0783c55c246eaf2cb5247e5ffa0fdbfb490fb0803980283dc5e8201cad3ca27a0de494533541297430d527ab74ba9625e21e6b2040bb092e45685ae2d4d927df6410b1009b520c475e8621d3c19e9213a1521f87f7dfa056821a4d653e9087de1816f98a124addadd36dc4987c32d31f9d7efd4557bce65616f9d8e8c11e16b4f02887c4616c558afc957d93d0449d967787aefa8319b42976e4fa9bd94b510a8e700ef7d726add29bce5e2b28ada272b511f69bb32b64912bf486ce756fd7a4d22ccd4ad90a45a26642e5da40049ad6ae0e174de3ea187be6187e74e7a69d1479cdd09509e8e077a3d6edd4115bfb045419535945c3ff311cad603ed5e4979e0ad41e7b56a34de42c86721033a49ab41e8940b9f7f58a0d9e4c90409421b4e77168dcffbd4c0d7fca8ff62813268e290e3ed4e152f715d12ce042c3c4153ab8f99f942df27d918ada63ddd549411caf3479dde8f5411f283bf2e4efa267d6cf05e7a0380cd169d2dc6ca49511760a8d53c91d760ee7b708dc29a3196986538e9316475efa3824339d9756a244e205c95715550b04d40e60abae32392d7f94c5ac45f753b2ff99ccc63c1a36fce4a456202d127aa2fdc87789689b3cb65f6fff05f16ffe3b80d2e458f5ef4c1f11d686bedeed5ec0d9003f1c1179fe835e157ffad96856d5d563fb7cf1a3f6dedb260ade6d42abba0bc30d7086dbfbc1639e7f376f222e0b260278992566ffeb3a3492f231b7474423db39e021a3f684f86397e8ee429232c7fdbded536b713e335eeb7e1ce57ca32710c7e11014e3aaf6cd952d728fbdfd4e938cf2fa40a84c18816602b81376e512e8421c4b83dd0a3d0a62440fcc302a99a0ca25e9f3c34236a6c73e7e15118f89382aaf2cd5e90dcc0d1d03307c44fa1e108fa1356845104facf7418a1b71f0d98101674afb4c03a19bf2fd985f454c74b8190c849c6ce005669f8a28626543007df6c40408aaae8e1cbd13c8d46d7dd5ae9ba0181b94b2556631fa1797ca27d5815948be636a1d25fe137f55b1ba48fcd4fcdef797c4006634c4d2cef77e7fe1f27e1456472808e91b751901982d76c9f227a7cb400c6b0d773c52a8bac428d0e087bb72129f2053b7dca0f83b9e2c0f01066a3d8c5fb903ffbebff46f99921601afc9c411c7447fa4d3192fb034e04ae379c667002ba062dbe75d97731e227fa75ab2299d0616ee5c5bffcbcb040452d5f398496d2de2e5f1f9a1cf7c91701d80ab1bc8e5c48b1e6dc102e0959c0d099b135fd3b036ceb1c4227bff04a575c86099b9983f31bb80ef619cdec4b901771511452d94028b2b7f3e9378506a66aa4662394fd73e00cc2124e4ec954620bde04a009642c9846a88a5eb7b94b290569e499216c14700503523768dc43446ac64ff50b6879b34e08198e3705411713846e21755b8377a38b939f0a9965f2b3434ecb73502d14ad527c35c5f4b32a595a31ccdf2e2e53ac5667984b26e8cd60e6543d9409ef3f3b304d2fdc8e432e8538464efdb9cf1a4556e957f3129afdb9f5628b7ec804cb2176936aba9c29f0d6c7b6ea426fdc852263c6ce5ed613e910b457f1a0725c048968c698a1e4a2d76a3e9594daadaa7ba0111644983c33c21bca64049ba37df4e4f1b26d3f33161ab256257daefd3464dff0f8674d359e4f83404e1fe6e1e77c9798bfd4122e6bda0ac44a72cee254419d5ec6707f80fc1b17e5d0d68c62a9935fcb925792766ff279505a00e3d09f21415fa8c341a9b90682353cd33ce8b0d0d2654ea58bdb5ae59a7601800d801c026835f515908ee7513d2ef272fdf914f42df2ebba5392a6ed079891475cfe7e0b02db858e666fca97d4267fe3b45ba3902f69685f6b510e8a25f188d992f1c7f0b0ac7a333950f8655d6ed70207caa2dd67b94d8b313426beb96554fe3e90ae30cb02670270b5b0915bed1ab295b9cf4a98e08638c8ceb877db8fe1cde210879330664c5f2d21bdb8d4da547e6768134e353bbd73fa5114af3a5915cc184bc00aee354035a6ff66c17920845527251515837d3944640643f3fae48a482f5e3d01aa65366ac3fc367f7974364f609906ef52bbc2d4352e8185f46ab9a255d08915df2e740ed025b70252907f1d00adc10adf0a32e1976292578baa7bd49e58a422e7db7e22160417f06b651756fe9da4e77b18013fb6d44c20e9a5517793b889d877769cc0ee21e23a593ba8d9b49ff532e9fd751d718726123204f0e04b3b44d3ed80d941d7f353d389d69ecf2ddf495891cc8610231b91f0e686f60193c3eb1127bba823b35713a47536e825e79eb3d7a4b76f40ed3f5280aa1e8ba6008d91403ff3f42288a7d0997d58f004438a3a330650251dc902ed82125143af707860c19318345062cc4b55e4bea11210e78116a0b99a5da151e75d63e2ed0122727191f38b8424e3aa093d5570fced718948b264144f8d4f34a287f8c38e6bad850dcc9d999ca45cf73a9904d54905cc5610cf0533952f6145ff1c28f54a01842e4669219f68e374ee20d8754db1a45bb910dd3d24a00cbf570b9337223a2a0525ef2037c0831056a658036afdcd4ddc907925a0d0637805f4b490c26debf633a2198897463872422a6e18ef9d8823eec2302b4ae766042ddb416d2eff4b297368ef80642d516919a3e727dc30bb25feae7b672c7b5650dceee878a8a5b29499fde05e4fc22442182fba691b7c868256f0edbd3ee6fa8bc86cff3c69d74bc566cf6c9ff55e33597f150b6a3cf5a71c6577d0d9d5f476ac7da78b00575b97292c193f640278e070ad47c3d55276b55204988085ccd00505ce6bd55158e300ffc6116e6b58078ece7b02807a1c1d5324ce410baf4be38892f5d45817a39ceeebeef65e5c22f5154b90aff61a8110dfb52530ba8ee9b35a6e2a7f9ad6e50995ce43d2b478036d6da3c10690da0cda60aa08d6b0168932721a15aac7c0f0ac0a30e29c31488eceb15cd35965bf18ef7f97c1bf63e9a137c488aad72f0c9dc7dbf23ba1f16da5d7ccba1daec8a5114577a1aaa4650fb42066a2d4305e81c3e0e1c3ae2d28f993c4e719b1d4cdc71064914057f25abb1892a9d320aa1ec8a2ff3508d925e8f74e2caf32a6565b05ee4c1492f8d58e4a286495b3aa1201a18e715eb372652cc06db225d03b35c9c33a1777c1bbbe8d6017db6312079135233288232730cec5e343f2833d69b5e40d26451e37f40207499ef4a3f66fd0fcaf2434cf88796f2dcb43427e1f8d5c5d4bff9af3064ecc69f1cacde4876786516a48969200f243d5bd6a4f26935ae28d747ee2a1a154c0a53be03d6e2c76f5d451df67f038eba901dd0eeb726cc29c6535eb456ed0e4d12b99631659caa4c612a626c1401a52a511838601123a904fafaef6581e6597ea7e0e91af14bc7f50052e752718d712fd0f9c4d5ce3e1cc65701ec4e2aa3cf9e489113e0b20f556b8ab6689b6097d924d7a8538cc2d5d977b838ed22a4f3f649611a94ca2912432863adaaa6742a8025250f369fec9c61a582d1ee00607941416ba35c1e262f9e2e0e2e92427fe8d79c1f8f9d1250b2a61f8abeb5fc93541d8da8ce5c302bdfc560f23d4f6a6b5de4bb160d6895492649fd591212abe2d20058d998257097a819dfd66a9f0f3f5e0dbe3fe69e68a27bafd10ad1880187238f109b4e91ada0609f9212ce8a0ef76378dee9c47e1adf81372affa1c09d8a6d2f131b5f4647a3b2a6e2c57a1af0401ebfcaadad24aca2f283461261a57eefb86d37e9e565970d476d8411905bbfd8828735882eed310935143a8b3f19c6bdbdd22571f15d3916893a9f044f3b0b01121755393ea3bb243e622d7edd0424966fc4697deb231e370045b914c92b081a86cbd41f039afa0f129aee9742e732ce35b67ffae17b87cc9bc2c2335c25e56bc23af8078307908945ca065b2005e59bde92cbc183bfa2b8ad2214bef7556140dfb80a1efecb7a0ab2d6e164db71b57017df482419ff93635e46c93df165eaa90ddcab8c2d18dd56f92cbdc716b8320b4b4f46756940b6cc3d831026fd8550af547fcff8d5253a40c45aec37af169e9c67fa80371b47dc8766b80c210f08c85325cfd373a7de4136f639f072f020cf4ecae3e0c2aa0f717b5a056a4a2ad3b654cc7a84457191f836087751cdd7f1177829bdae057bd723f6022500fc3b6fd7ce3c82f6982a528e516e7224e94cca24d078b650d166f18be6e6b2bb91330ff2652a58182ee23976014972dd1009f4c52b259ecfa1de875a80ca7a6b0f23441d936f7f3b02569ecf1aaf53097ca2d8c213a9d4559c2fc394339410d4277035ef65ea0954964430f5b37f19302f1092853ee676a3259740021a309b6c022acaceb797644004e8673a102d33818aa1391293104c670a5e9b4acd963ff39179d41d68dae2aa5afcc8c58e8b2e8c2f0b59cb5d6cb3b5fa4fd0ae7f4765200d13c4a9085b41631880051e8219168994f7b24019ecc7d980f81ce8737da9eea67141803cb6655b6b8ce3362b924ec96c7c8f67138a0d329cadc298703c8be74c16970d6dd560bdf0a591f8965caec59acad4d7771767f25179285cca26b5f00beb9d54f458900f4689b639811cc25d8fc422f60a41ed9f5e3d2c70b73d2c16f41a8068dce13c2e154c2cf254881703932e511747e9697489bacde54d945eba7813d5a4084289e1475ea333ba027236c27466af665c8d42cdb6d7e67800c410edcd7bd8f8e1d054eab1f16cf10796d80359e71a06336ac769719ae0d2f30c67a56e42ac895bab5b42e8ece1a4b74a0dd6fc9a8a3f7bc1ef7df493cffbe837e6c3171fd63ea38dec3c5162cece81ff8febd1db558a4188ca64fa930ccc2aaffa402625024b5daca0a081112faeef89dbbc0d58c6190cf2e98f2daaf95d432b3ac0cb7bfcaa07e292a1b0a9cf724d51066c64b2f0bcb14adfc18fc4b6acebf705d79936fb67c00f1f9102df8b0abfe226595e8fa0789040cd535eb26344e5c0512186ff3f850ee6c85b732bf99fa4d1432a68d9d6675efbb36028438e020fbf58d3ec6259e42a5ffb264d73ecc107a3113b89919b6a83c5de243c9216979badb81a238da36b553f181045c1854df11a2914d991a802ea22775b585097c9c84d15efa2a8b0d15403d0289eeeba203fe987804256aac9c697cf01c8ddc9ac22b8d3773011f1b8641f273c03df980361d2b39a038ac2a6d7e3d7744de26bbaa3254ae9bb914a222363cb80d3f50e203ef87158f9c26f80b753222b016ca90305dd78baf95885b096cde572d143173c9080403de7037d5584a09e8fbc7e48d450cfa39bf327db8960a95b197f60de3ab6ca8bd9bdf0239af6da5b0116c4c210cc070a517c340c999a0ac66d30c45cafd206ccf7f7ae66be5f3f599e3edb7e34a26d9fcff563e095ed48b2a3bb68d26a3489612f8856656295c4a59e1293ce556a1b8719bd7936357951fb7a8addce5fe276f4613d1b58eb0b5f68daa2d01d75f55c5f8a89119395d38e75dbbf042fd0c28e78bf9de0249351798007a154c213d7065d5cae7bc815da2c3e898ba1ff70391e65455d339df8d96411918494e258b254e1d8caa00642f4cc8905c07ec2a6e8b8c0559f21b6a969174a26fa8dd8d0da1ce00b4aa0b6b88ce61657df320c7aeb590ceb30cb10fe5919f9496e530aef05c4f0e9178e42d1f70091d40cf295e7c242b3888b6577bb8a3e0399709c6734e7724e1208d63149e498ed45fc51da6dfc3f721413cf47f3ce702550a0ef039da74d90a6ea66049241a1f72ae768b76f4f16a15f40d0b34ba3553d753447d102c4f558ce2b9d668811e0a64c1f43ee048305622bcc1191734cb1236f05aee78a628a73fec30b75c23e4d717c31a67ef8e7370294f23d9d18e4e6ce96d2d25fa03e7e2f74d946be05015d59ba7bfba60ea2dc0a0b775a309c34821b96dfa33bdfc8d3b83257c08a981e872739ed6c50a0d42b75735c7d68099b23d371a247054141af8da56e3ed25131f94fd5bfb9df839dacccad9dfb716d79387b4f615d50a5ca7ab56f97ad69edab58cfb7d47061b8dab184d0c0e7f080802667e6c41a9de4113f9d566d58ae6a0e79837fcfbbf0e216ea5d00f5132ea0e0f7b2a17205d8789ed0151f88e44b1910791369b1c75e75252e1fb1bf344c143f4b91e78af60cec976648e15730663daaf234b5a74d2d12fae8d68a85b7f4b5bd1bd7b98daec098f5b522adf647aa15ac0c4fe8c2b86ad4c42561a2458b5b4606057463887525abc76212d85b36333f51ae86186cd178374e6365583fb156306479376792d0299d6919168aab16ddedf4754e34e2b6ba02635617c99a7b8684dad8ccf84475c590f58262834588c98996751376c58256b736c77b53b4d2cadc447661ccbab2dafbd748781486d5136f1583d657d9583dfd275caf614df15630c6caed9fd01edc4a886aee96621a66ed2204dba0fbdf88d1dacc5e43db2f61747081de647e35cded52ffb84a6d18efee8550548c4eba058439c3c9d9d2261d6c97e4e1b5b7fa7cb6ff9f14352538ea669e0db193225850d3337da62deec72754063a73a9fbccbcabdb465c40a9cb0810f0df49932301c2d714d5165ce72a543103b178749b7c596e73090fc8e367ce3c07e3460301173330124b733ce7b6ba264413226a0766fb59baabfad2666e90fceb954061b17294aad282ceeaaffe9ba9b8bfde08887a296dd328db1afb5f234ba2280982245340cae5a3aeabaacfb14d975c30d91b630fb25e52e0410cc86b79900c510fb1f2cc448feb6317e7c1a61973ab6969f38910948022eebf28b6eb156bdcc1e17ffda2a85f5f4cc25f5b475a25c852e08a371272dffb23affd99dedfb509813544da46453dd0826630e15f5f6920ffad307a51ce97f54805e7413ff8411dbbd56790b26bf5b8b92e32bdde4e924081367ad17ee5693d2db0e2464add6f3842402a204c5371ece985eae7a5822323934868cf9105b017d6f4731cfd0d36b88f1bc5d4738a4157e42ad09903060382b752fb67d31691ae67854127f37c857ba0252f76b77281bf0c7f942d7aadec63a0314f2d3e6bdd25f246ff0aca5544be68500606a2a1b804733d13d07d2edf6660db88de9f244c70d1713b025bcc10cedfefb7faf09cb8eed3ed38c129c352d192f500a5d925903294c5865bc1a4e6ba7c6f790c001c4108d848f3eb06f2e897d7c213f3f598553657707abc79044592239108cb46d3c19bf2e2b805981877733f07bbc6b809d339c3a6d29b59ee9f45e0fa923c15a558504ff470eaa1ca71ea3408b2f39c43b82b6fce09a8045d1f16fc04cce411116d4901fca6a4302d3d9812c5fe779207dea078572e02bb4f5d1c98a816bef1ecc787c3d418d1786bfa7e1afd82dddb0ce1618bf91df4833457b9369beda28315c3ec775753c858a08c9cfa51701c515cc81367860999cf05208246a35398eb71c15f2fb10deac4da8e8ee7eb96d3aac863b8947c36e72c298b2ff870d2e34f647f66242483386f9b4852cf7a1ba66b2bd531a079cdb71d0b6efae68861668736a4f397d905287bd67f02b1412db43b3aa26a5dbe1a31ec36ffcb7682c366923b24f0e962e4e66dc4b9de3540accb05320d1a98a54e8efda3cb78237c752872e14b6f292453e3b3c17c0626d2361a5620102418ebc03bba1c7c21a40aa63908d574014aea9ae602e66f19d2e04d58e378974e38c2c47f793012e0c5c6c282c7955642d95b60021a4967460c2593be881e3584cd1ecfb9cf4ebe862d0acb8ac93b924461ae4e6f9cffae1bb105d590a6ac744e181738c166fe77aa48404056f80aeca60a7b6dc82eb86a037d005ed6ae40f6ea839aedd844eecf80fb3fa56720a00b071f357bb2573ad90227c5d0cfde030fdadcf86073a77d2cb52dc76b5566e945416498e956cfc9b1b7d5e1df3b55565769b55c3977b1b4ffde311daee958138b3bb90c8bd7de2a3e6ab82990e126900268dbb200e772323e63f5e4b7dec0603909d81c59412e9ed9b4032161e1bd530188c6f08042a4d599b5824957a9cf8df6a9c922a7f09c46b2358956c7c1a5926f7df348f5754f69473e2feeb89b0181371b9a37bbebd08ca7c688e2bc2e98eb3378a0ac65b3144337bb6d9c2d82d9ca1c111476ed496206e7dadaf8d4f9f84af1a911b4f25083dc0c495de58449655991fc2e291390584e4e419aa767b5af7d7e798cf8193270b42b3fc6d6023619ebd9ae9d7ad2607ff9262f741fd66452e968b21c6e1ea28e192a619789f0188405d98c570a5c50d8330964839763d90e918d09bf4be5b3bedba12fded01d186593a7668dbff4c98e01c558a4d75bc284ca33ea052030167ed702ab14fe3f784b4216098868a932ef926f2abe774be5d452890b678720cafc7ae7427ddc9ddff9987372298d1c1e48f7ce91a418912959628905acee158a2880209210dae059fee0467d04cac5c4950778e045131af19852597d57faca5d29ad26bd5fb00a0e8318225d4a8e4c33346f01766c4c368152590507e06cd9120a35d7c5e671837681ab6ae22643b217fa3f39a911c98a94a2e845d8fa2176cb83ae618183554269d24965b903b02b4336f94e1736391f40bd14230201acda428168c175e93826c65932804c48cbdc604c99f8d8d001b61d1a76b72408083ef22b8e248c996c793e2f87cae457ecdec234721a209bf8b6e564d537f4ef06915947b1f2bec44c9e1d729394e2feb9c3d443e9401354a6ae9662e006e4a1ff5c26fb08c24d975651d70e98c917a021058ca168c6302af24911abd663b27cc110b319ff3d024d9f576ec58ae25d98e6e46ad105a339902a65fed864585ba5ea90d74f4667d081cab83dae43477df179a85f2b4a8965f9b6704f1b91a56dc8dcea3bce4a4a774f51384fe36ca17506525a61176882e4f5ab5a03a487a896ad12a38f6d135cd18561ed184e77687728d42a25cb58c7981f40a40967d39706972a63344ea2e8cdc2510094e8b9f63045734cdd715e383a89cd28f3a826a7dfa1c20a498c57f2a99a377b53349dd2eff8095cae34cb3d8794d06be70a20a49ca9ced21ee28889efcdd9601ea599131489027d9b6181830a2de5b493fd3e8e419ac61ad50ef56f9daad3a89d2fc5d238e3e7a3f2f38ee22861d7f9abd598c9c344c2cf4c27a4def07e82cc1c6b278e340eccbeeab1bb152feb7a0e7aa3a8e3c3f2525e6ef251d12460816a7042e3b64757eb358ed73700955c28003ce37b09afb93a0a43c1c32e130a5706acd52d18e6637279a9751f8fd35fd48e764f13ba2c65503b9c2aea7f79640a5832a9fb39fe5d3485c1ba4cc1f566416844302d5e3fa4040cfada5ad8767e04f6d47cf763e0ec00ef86515a9129cc5e3f8a3b0d4974d5d99bc5c44a11197c8347faa7fb728eeb63e8d6c8973b610c7711c9692900627c11a25ab1c66b31f6fd9106f8b63c8544373dccc92850cb7283ad3da68c9e7fd2a0fc49706261cb4b9769bf4dcea62500c7a73079cd880281af21336882065a5147fbb15e35e27fdf10b7f4a2f79f793d43a90996e25f1fee351a9d9bfe07516a22e6968e37c4ddcb221448658a35c97e690e6788b01ba372f465582ce6a14a708372d7111787ef4eaa2b62d6a6d836ebf4b44cc20f8ee91db5c412458601365fcfb26931fdccefb2f1f10ddc788d397c0aaac00f29447e4e856aaa57290950e7adadb6a5ba8d8a116e93a0e6d79f8399ec4d9d8a1df7a7236ea113586fc46dfeecef69e4a6a251c7ccae7cb923bb7e5f4bdeef48fe5ebbdc065f584940653660899d6b76a5d6afad00311b0823e7bc659011e2c32f1e4f0bf3688d8769ce302d84512ffe2dda04de31b1033fa8b8773a03f2a6d182c95a0a071d82175ca286cd269c2033cd60617a8244b2708ccd31a1928a0581e9f88aa7db7eeefb38b0f08dd5709241edb967b3944060f826ab9ae02f72025797c6c6b429026d5a793d850bb71932f747cdd26a4622b167979bc335180aacb8f200d43f8d64fcdb918877ed6d3598691ce5c80598cee92554d4e76af5aa45dec98036a99ee7db86f8ba6dde473416a37e98229f89df2ca28b665a2316dde7d7813d536313cff9618b386aa2891a719e80754e4a681195ba94faf0acc64dfc5d8512cdf1111c81f1dea670880a9157417b9248fa2009af795ee15803627ca0922f19638fddad1868c6a63d97653ebd86c34794d796b3cdc9f7f8e59a21f2493eb6c8976fd0b6f5fda0460ddf45a2f60e4919b3a7c0d1ed268b919cac1cbc3bd4b40b666f1228e195756389d724c86c2340e648d84526977f827eb632d956c70334e7b5a428251317a4140a9c9f60ff73c67b5dd5888675245a7ac48316f781daccd766513651fba385b9a970b6f1c4c15ede4fa4f2306205e911dec8e1b48266033b122d770a38c1abc7a46265ab3654ac858154ff0ef58b607ab4fdef6160748b75f24fcb5bdfb64f50c20892af090a5e12b1a2c352a1087e5b0af49e8eb813a3fb83413ec825a53a359c2aca53cca5a4fbfe408e17bf0399fd4999d7dc03ef0e560cfbe96413f31e2f4596f3ab81c72dd35c28b6591f81a951b3beb7646ac91e068bc91391a5e364ee4b819e1635a034640ab2ec7ac50238cfbcb42f8b4c9dc78a63eda5389d34ceb0542f4aac895ad81b584d9a2305bac88ed735983b46b91e23baa36b57d10f02e074830f8ecc3360b94607abb68d58030fb9a62e399bd1e974d72c9712b704a4906f22587fd213c15e73dc058674d06dd4239e3ec5d27563a46a688eb45188c54dff34a6e80ff917cbbaa5f3ab82d9eec8afb5cad1cb5358d0c68570f9ce016075278c18cff7cdef587ac4180624a15c10af295e2db8dcea2d9350683f0f92caa8729c32ee19fa480bf66731637711e9f965bc59c7025c121447cea9c81099c28c9e77188b44097d97c67376bcbb9b09e91b4b19d0b56f184dd640bdb4f2e1f7eb0b0ec8c7eb36fae82024c18f4e7ec5edf70d8915f7dc3514d47e45e9b57cf1e7747b2aa168e195af21aa6facd7b6e6ab75ec0fb1fabc0ca179e271cd2353cc4ab8cffe57b4b8ed6398e09323ffc1a776ee9f32c29d053fd8474f3ec0eaad6fa03e3ceb97fb21b11a18abc43bcb9c2efd456346cdc409f0589d5642b41af9517b212cd65444675bfeabad00567619d9e370cb12a2d3f65f51f69dd99184f1ceb5becb81a30027d10416961fbf8fcdef94c9701e04ee49a117321f454f8655b67552201c8d33e004d1504d372b34e8346250480baa3dfc68183a5de5bb284adcb23c5aee77250e3a54ed4714e58bf26038243f1c2b03449ae9b7ea7accbf49a0dc0daf45023d33d6f303202493b040512afb56727b0bff44d923263fab297ebba14f145215d95201e1772704eb53945f736b569fa2c4e59390dbc4d23be56f4459559f1901273c3951c0e1e92453a2b94b0677d2b95e8aa29308d3248a622344b91842047b7ad028d1f1e5f8a4aa851d0875f785fc53e1c521e0189f85703e537c9cbdc340668315505178b80294449143f09e25bc021a24c48747e5bdf335ad63e9cddb47bbfe029b5e4998c24559bbc07ee629f5a21622bdf32b3ca2fb95faf610473a872300222d98c26b6e705af91584312f522a82fdef33379a2a01e2597e851764ea447475c23a572292702df2c5fab8803c3bdee8267d5f6e011e7b692647f0f3ceb2a74547202dafa2127cf7e48390741307190bcd3b10af12d6d4785d6ed76350ebca35945092ab7b4093f70bdf6557a0392d56c00b340e28624ea90f9032f04bdf34d3dc600f3d1ca77c600615c437b0b4b84edfd342a62edbb1225d7ef8ba39576b11627153b3d7162bf45e82720ffcec2bcd57db6de693fc896a0897e3f50861e784c2dbbb4bc20fb8d088589d42d6f6d4a5902d59f006398d8b80ad4d75d1df72b5217c44e90f83b7fa228cf47133a8b6c968c4e7f3abecb3f335cc179d997f8a21315bea7701933906c3ba34ef2466ee37159dd6cb08cc1decfba9d1206df676cd24e90fa1c68d8f014fb7eac2bddf066fc6ae502aee213a11543f95734bd203c9ee687b0063f6034cd0d7403f398f784a53a1236db51646c026b5f4cfc9599a2b0a6a287f3ad3983a1bbfb522078fd83126e5e941440843baef23a3cd2da639bcbfe5cdc1249d9090f7adfe92a4a1e6bbd73de30e7a412ce5228b03886d516250390a4f8a46d844fac842cb4324b45a460d3f65cd44b3b0d578d64489272c05f5d25288981edd326f57550b3fed941f5b9c45221484204b1d38b991518598fab952e311f97592e55b64fd93ddae40e5d3a1e52ac94557e8c14f6814f786ce6735be0aa5c2fad4d7bade5d27249c14f633f2128a87a5034a4e94cb42f3aa280f61a936b7a7a284163c462749519029c4e10773a3250f590b5dccc1e8aa6d8aa415b9e04bae43c8555819e355ab2ee1e4a4d1ed157304b8da0e3ad0ed362d6b42361d26e1fe3136e6c32dd51361fec25eeed4aa7caafdc98c0b8b070163d907f609698c28b2950abc3083817d7fbb834913d1cfebb00a9651f7a483d3ba6fcbbd96ec7330cf92883e591cb8075c69595d1e64c9fb53da89f3dc9a8b5163ae07a34baee74bf6a519d712fa1735ba1832fa1bbbdf02f4c2253fc69968aef5a2606ce89e3137aed7b0fe3c3c2d44b3716a21802f1a0f5c969e788e691498d4cb6f8517810ecdd00fcc65047aaba1c8af7a7355ddac2b40aeff72588af02295aa34337a33f4b29be99c32c8d170a0593dedc547501d1e8359d3ddf316135c40ae7ef0747e27e9f330d2f30336f2b402d37f0cc7093f72573ca49dde17eb6b6b55b309a2f4dc5b1ac92864022eedfe0cfad6fd1985b4e13624915d95046f15e942a5edf13686671147047e90bdbd53f13e0da5d3e1355159a9dafe3b8d08ac0374d0c3ca7c97b44f64c125aae2b62e9b62c39bb27447f2794fbf957eca1a426efe31f061c6f6dad48f92764789eaa70c96c90052f13f71c86835d088088aa503fb3560a9f6420951838ebfb53301b30bb8d87105b6d766c1ce20ebd576a2ce8e5a35af6ef1076a780be101cc41884ad515104a99f3a6d00a65a83a4ceff06382ae7da0fb0b485adbec2453844a0454a62911be2819f909ce5e97a2c06f9ec1b092c8b813883bc72bd7d83e007d3a0dd06c2069d35b6331795e3b9b55b5fc5c405600b84e8fe2d010911e52a01ef6577f64c8ccc4046d0fa3f0a07cea69de22df78e467737f5827f179bb03afc19361c34f138c920c8997b47603e2879a1fb4eb1fdd72bfac2567ba117ba4b02c619e5cf2149aad4d710986e46283ffc36f16fa3d5d82854d10934471f513362d4f857a3b545af4d984946d4cda931422d6829b162d01dd4510b9b595569ed0b1e34c2ed243d5af0178a7526599afb2eef84c3efdc1c60cacafc9cbdc6bf141ed20fa6ce1399551fea8cdb9e1ec81705e32b20e263a8434e6367b853591acdc158611e9cf771bad81ed87b5eae0541ec148c494b3e58d7d8b9d8f61a9b62181ee39566f19e30b1cd528dc4d41637086cf9ed3157d77895b19a35fc924d7e6cf37308014e1704c5a32aab94381328c98afd7b34dec35b40c49b9d74d957070b9e83c4c66243deef25c07bb6c3da9fdbb114625fa079bd97ac85edca586408ab78d403b99e51efff583951a5f474f8aa01cdbacdccba169cf636d00bfaf84a3dc261ccb785cd0137dad4bb03579264b15cc7b77927fedccfc2494432e7ad2d2e8c6fb96d14c5fbdeea64703ab6f5612c4522f774671b9b8d20c6067853f6c6f69f24100c2be4e73332e8c3495919f34580011b7bdb520d5245fb667197791b0b3fb07fc0c7f32dc67748f758e21532a4d248889008ab0baf228787bbb1746d7725c0380c9cfdb456eb202c61af886462612fcc81617243e161608ef8a6b5e094916dfa40219e66a005fc67256c59067536fc583be4a8a05d8dd14bb1816e0918474f5576fd83cb1e8ef300671adde300b2d72a5fbc425115c2455f4020d12fba9d2e239bc5bfb1e15df054a7edb2be7f83828fe21a124bd9da134a4ef1132ef04d7bc9f1db4bb739c0a55ec30be6031115c0bb0fb505760fd84a72a7754d844902cca46622ca66a78a86298fd3d8f51813747120f085f6830a7b8005177edacb9d4d452672ed1910f02601f97ecc438f53af591cf52409ebc64cb1652fdf6914a3d0c22bfeff8f038421edb049435e849345c87d3472b95f13840012a1632b199bea8d2e952e052067a64094d068dff11a48e14b7f771ebe2159ada3a443d1a321ec9e596191d812ec964d60b2f515faa63d44e0070f31cefb2fdda7246a7c0a750e08aae2521900bfc15b7f8ce1103f10b45d187ed2f8b0c5410a0bc211b03ff3aad1a3837e03d1b848a0d714fb76c12cd76de4de423dbede9c1f8d6bab66b7f984c20ec203bdab674dd5dc89c5c19abcac960b251ed0d517d2a6c208c559b3fd2c970ecf5bcea7373b02ff6eff46257a0e61cff53dc362262c4e3d2d9316e3bfa311637d07aa697bf586572eab7d39b9655c2b0bd376f76272a2d39075797c73a1cb24fa35802d97bd2dc14587d3091aa1b6c8927e443241fefeef640c4c4f4920ff5519fa5256e22d1564fb4cf30663e4c39dfd6ba5e1b1f94e24f9a59f47ff1549e2f21a026f90eb701a94af264087473efb1375a82ab099f323035d7f6d0b8c2647367e7cb4dae485eadbfdcf69ad0518c1cea17210a281b9e4d0444454dc9c79220ae34edd8d796b18ba5f70a40a00e9f4bbd75897d2e25d64eb20ee7f2e26ef3a0e90e84840ed1686faa12a74ed88fdda33a86e6cddd65c57ef5dae8c4f959e2b92dd276a4f0419989c958b1037a99656469d43555368af1eb5afc2fb127f227ce4f10a4e08eab3086f48a4ddaae848279df7807fc7018a3fc05d4279906422a5dcdc5351d86b17a3a1450ba92d10ac5d864fa597c34f001e7beebb75633638fbda52b192b0d302a6dbc8058ad479bef16f200a7b0edb8cb552e6020472a601928db1cd2a87c1d93c95f7431b5ddaa2c8a10ae9363e95d124c53c210ca93cf9b5e8d8d12b82cd4abb5be6c02f4d38bff4c6aeb1a1f83d0bbf61d60af81de4f7b45e2f652a342c730fc58f3b1465d68c86397a2763440a4105761f295ac29cb7948cda094a8b8543f7223378c605e3b22917f4217cd8515ea2d098567b690e236e0951599f228a5da1b917bda799e9492b16bb8d91024906f6d7cd90f167c4bf9f7149918ac33835138588c996c48191fe3c120f0f6a4da9a6c2bda0f9c0082f1fc6081a53b5128a24a6d6d1f80ac25d2f2103b6ee5864d816e75b7724b412ebba1b4dd081d7909e81b02d4f9631c04de74f1950c265d3bc89bad3b198df4f3e97e5ed2019daa31df1169259e4138dc74a06a43f318be7385e2a6cfa1543094456b4e1dc8cb843cd03e9e7ad23b1b4558bacc39c0cd332aa56bb4431ca12d657f7dd0d71582bf20f17d7465f6b6bd45917cb1ad9675e9bf1c469da3ef59459b5afea2953ac72a2f003b5091649b243fc645a55558f8871af60678b6c5f7425e249d17dbd03fda4e5652a9f05edcf9087cf17e7c3f324cac8b594ce2f80f7ecd0bbb5c8f52db0350a9924108a46d06f10246585b899a9931c0169866bbcb74e5556e85640661c37300570a631adab3c4433437970ebabc01cbbfd8e483dc595eed43d8c7443411a200e8304813a12df01ecc1ac20a00fe65f107e38616ec5eb6c72a1b321b2c070d75c76247acd3293a9d463b31820f6f68f005b2f61397b4333d56a876ba2bf4c3628e4b5f6dfc70d94d1b135695ccec1d66aacffbb6f4c64ab5a85866e056a09cd3528f3f6138198a1d1e7443d0eafe4b859392153881303b86396e577bae4faff863b2c935e102b9eb3c29cffe38748be85ad358b11c82b739847412eca04dea31889a04e3da3cfbc784b04d25c0957d9a10f8378825d694a0dbd6d91f4cf765c43f2b8fb123f9e2bdad1b043701496306cf5cd0ca2486f82c7119cf7fadd50835441fb4631753a93b656d83c793f1da48d34f95bce3c3bf60191ac93341d412aca8c2e30e41d74ed5854f7365fecdc5901dbfe4795d7cedb7d654a6040737975961936586605e977b0910083cc8b542f634e904ebdb4646961dc39feb43efa48223c27712c50751bbf9f9b6a1945b75875e57a51af47c4d965379ab5f438f9f5eaeff6a8b9062cea2b337dfccfcc6638fa86ffe4cf0159c524c527880b6121a2883616a96e6a69a128cce1d31a1f21879c658e6762387035766433a010132aa012ec03696ba4a7b5ea987ae0a02cbb666c6e0746fd9a7e423dbe2830e67bbee8377d2268c9f32584ba84fa97fb152df399bffb2fa8b8d39b5da5064ef9bfcb5b630b4de90d8386de66a85a1541f362135c8f20c69a5f25a93213d12c0162bb65b16bbe5e043b13f44c0d09fdb738649c1778c68dbd5b20ba616f5e4a68d2325bdcd70a387689679c72cd69aeb6f8baaa1a18aa153ead868c118dc544904a18614925b67fd26d4893df031f636e9cd225c22cb823962438734ee5515adc9ba585126c6bb5f5bacf7ab9cf0e98120905e66da838676c747cc2a20edd260b0c73381abe460ec1659f14e200abb8dac8302df000113a7f233727520d65c7c2e856d9496c4b4fb954f2c10ed85b8edcd2bbe06020448d3a1e97df5745ca67c135d9bcbee85d3221402ad42b61f3bf3713a67f30b5ae7e04cb9cd373b76b1ae9fdbf240b4de80218024882c710e1255ebfcd2d584af8fd5f3396455facaa2c19bafeb4e7e8787a81b72ee962963a67ad2b55d086136106b12d7842c05bde5aa6409fb10b975d92f4d5cd2b5141f560c6d272d19cc86903ac8246f487f53fd8f6cabc049c7415a7cf4a7237b2021e8acfd11238439ee0a671808966b7622eae6e3a1f827f0735b79a25cdefe901f3cb5e42640427dc92f3672e787a55e2049df9342860815624c0599a4df5797c3ff7d48cda32756a24d0336cd456f9a149733ae3b7de8f7d5d190c252cc3ed6d247eb7db5f18ebf7cf70ea4057c19b97c5b9af0e111cad3b1aea6dec7c61536688926148f7f0c6affd60caef8de219a7afc36866b6fd6e93cdb16063898a8583044b69fb3edcf95077436c7dc555a0ec82717c15ab28ef58f353caa413c86be914f7e64974ae779afe6dd0514a4c103856708104ac8bf172fd2938c38814a77e80bf8c9009f7002bf6017571e509ef9aae85c56be504bf948c80f22c3021b9321ed8c406e0cdf45cfbe709d05b440f4aad8064f12de22b18623de896a372e31c1a136737a0645f39b6fb827ddb5845e9f5bec7015fd8c56111d9cd88acbdb9353d1039c2ac98ebe44658dad5f60e1d77121c92508f1716ac05b52f56dd7ec9a8cfa75433436555daff5578f63ccabe764321b08223cc62fe6f6f1c692a446806576bfd962f47b28996744511ba9cbed3abd25752810ab5c945d57829c86c618ae94860ecddcbda19783610c737f31d11cd9b391f22c95e464ef5f0c9a58c12719d9c8e3efbf9f178eefedeb19bb50a8743f4634e7908cb1c30638ab52caa194dc51e831be9440d3688fc5b8237559a68981e7e7ecfaba68f07385b5f4b2fa86667947c34e83e468fb07af0aaf6fcdaddca69bca2fe3f22c1741bff99070c4eaab0a1c92cd228e46b78fb2ef2ec9723f5f60781c852632b747f5c8c970166318f280a45b46bc6ced8d7c9cf9450fa73b9b6de50715bfcfcd7ecc189b35e1cfacad2081bae54f9805c20da709c06e0b91278b07935c25f9a831211a7ba4fcf852c456a338c873c0e59641cd642aa6c7b670f84376438209b3c79049d3a4ccf0f5f42a5308b94a985c8e936f1a33d120633dd1ccf1092edd108f85757d807f48ab9b1b52bb2ceada1126e1f61e881ca060543ce9c4b2b5acaa8d600ad8604b90575471afc6be3089b94a832386144aa721b5d11a0289838521561d72a8d9028cb22734cfb8c8f6930a7bc9b6874f5f43213dc3d4557590534594f11af324b57ee9df8a56fd1ef4a3eaabfa5858326a76bc1e0dc691c428fdd418930789b254ac2825bab1d0fcc392984835710cb0d92a5fe0cdeffe4420ff61d058584cf11f7f885d4fe8f41da4d4f0a4ac93d490d0dd2687e6e2957a90da1fbd4730c7602a513fdcb878d715d361ec4a4a215050c15d0ea736da88d8429d3a701db3bd1256811a831958d6343046492426e58bc24acd405cd703afafe30a70f056662fa63396f27eb60c80b2a519449b5a0615a819c4b58b361d169466a8f3b328d0fcd8cc5a1cfdfd580846f088b007bdaaad8250a0e0b0b4492d2781f0eaf5216f77956d5f4b06dc4190c9e5f48515d648e2789e4ade91a6a99e6f77a4c2d31d4c0780b54af0a0c94a7232cfee6679119f517a72d604647fe9ffc82d3715e4a9f71f8b059a73f6a7b64e1a8d3f9f39836676a328976fe26fac527ccfaf40725fa139a3901c022a3a7b50f381d80725e4b7b23f5981b9ef3c5424cd81757e52ae966536a3dd7329b4e48e517279818f821941fa028f4504376598a090573548bb9d1736a7b1015d0ea0e00c7496c49310720dc27fe1ce46ea8e241c055d4cfe48d551052e6eb2b77b8df09a06812001a2c34a6483c5895e1a0cd3bbe492a2ea6509bea6a75be43076c1b85411584b1d4470038a63427c5b300eca9a0ebb9e8fa07723e04231693500c25bdf0073172ee4eb7de6998737c1abe1444af97d0da55d0b96d2e1255532a702800260a00ac0208c7cfc127ecd2d7cfc841425c26802e6a26e9ac111ee3c82c2b6ddd6e4de52a69464c507e107ae075dbee312e43b2e68beeba2446846b54c6180f4965a52f8a6cec5c027a8d450d63035949c87927a7fd2674c4a2adbe84736f86374437e7f755f0c9d2e7c9ce0b714cb6e16abef76966b46d3226af8b55abf01f8f07b017d47a9304618b724a071b8fa36f37d4587efbe52f56da3f28dc5c737961fbeb334f9ee2c59be9dba66b418167c27754715c056ac9e19072faf57770f51c20c23e57c77d768b9372b2d3cb373dcc18628446e4d482924c5960451eb6163f3a38d171d907075f789213504825571a300fa7ac1fa8d97362918b28399383b66fd80304d515fe884117365d65dbea5befd012f966d16d7530d817eabdca2cd95a6f2705c90ba1e284697ae199199ab04a4e3d88fcecb73f62f58a303a2470d609dcbb8dd2965c75754aa86a882a6f290f4e03b9e3c717cf8b22117f6d1e3e606675396c9e87e9e2fb9fbc0f70c86385e8236beaf3d56d63767eb9bacc949f9ed7c7279b86de1d0d2db16b37a00c0ea6e284ad9713687434a29639c1e542bb08f1e5b7694e9768c31767777b758b4e3a6572b11bb3ce2ba8cb1e759ffbb3c78ecf628ed36b1cb63978794bdbbbb3ce44b16963dc92d00f4b058cfd1f6287b4ed7065276946382b266a5c36ac95637277bb2e3717d2d9647bb4fc873aa02450418a8204a802a33b3407d613850579e40d5ba0b5413d4125511185dae25a59c1c33d790f8904a9c98b839514d146e4e6538a7caf3f1e6c4a5847ae2e3cd094a14eabf390d1180a48ce4430807c597ae19540613cf9ec21fc0810c0ebac4e932a789e2f21f71c3c302978a8398e86f7406b5c1aaeb5adc94cc3223a7c23ede86d4e19854ff781b9246aaf6e83081d41e2bbd068a44a27e1f6f4364681a62e4aa62f0f136048948a51f6f42e6b495baf3f126e44d6ba9dcebe36d498e947fb80ee358186f43243d0e7e195978316e63923633cb8ddd0e19e796b2bb27b8cc31c61863036231283afb327337f7ec1863f429c3cccc3c4eea7253762d19642f69fdd846fb24255ee2f2cc4ccffc143254c83b9804efc3181a9ce0e6cbd2911f2f8ce1133431840b91365547a07073e18f7d80b08267da26c9df26a727296888ac704310138c68c12cdcda0f1612d6f048fa92c29959b8505b3e5cdb8609576a970a88e7186e1859f0ecdf9722c58740ffb9375114319f0628a0e0030e4a4ee2cc2142410b44a044f93295a4e5d9a5ace0bfef9b02e4bf293ffc678392ffdc73cdf86407789b142bdffa2535641bdb6c1c14ff262535354545c541f19b6a80f32c224f8e956d469d46a21667b2d066b506a5ef867ca89ef3e99ac1c9283e1c800e4d1aed8b62c4c5d328bb5be734b7bc5c4727eb69b71b8376c674fed9809f08ad74a58a68d72fdc47afa0fcd8002e728c9490c4cc933147567268e26acb982857c0749631757a4ee888508c08c0519125363748f962244c950dc091a3060574d42431692c6382a0503702000a181815d363c91983effb899aef33df50a87cfb0555aa8821cccc285c33e4ed0990d71a24684c2bb061921267c668b87449810b4cfc5082c4a61444477c71626b29f856c1b7fb782b68b50810cb8c69400c323626243c4731d141481513384dbc38597a76a12227639e5d4692bfd1b0c40c26d3e504a41daa7286333cd4d675bfdf872f1711cc872b34f8d0c5b3922555c90ecf0e2bf2f1b62487e719969e7da7a8564e810e6e380198a9f5cd77bbcb35a36996a71d3e257a20c927a5ef2cdfdefa7e24c1f22ca64c1223056031616207867be7a0af8234410225aac8113317624c48a21203123360fa4a0e759efd013ce485a961ad2b8392c97ca5b452065ad0b737fde19999999919c90e9f80ef1f6a28eb0b8664e9c5b3fc5c058e165ff454988fb723553ea4577e7dc5c7db11a5f77816565563246aa518b4fec3913fc622faaa29b699aad6cdea5a5330ac5dd77ea7b824904362d97a0b851a7ae0856f37f251635bef19527f3a8f6123fdba177baacbb49495eaaf44d5449c095529fc7a11aa5d8e1659bcd05422f150c8d188f60734ebcd8687aad6db8a87e22c0f0fc55b0c4fbfae5f97494cf4cdd6653347734a7a5262b2f4a409ca53945a0d503c146e1b1b0f85ebe6c643211ff9f50dd9e9a77828e4349f8643e2a1aee28313c2e4c4a4862a5bc2e0f02157e6d7ca871c9aafd5abcfa5d9d44f1db4eec443fcc44ffcc44ffc5483b2f193141f11922164933679e325373c6475656565656575c5b4b5e2a536914d921f1ab1831e034cdbaf83d6239b599c65e0f316f843c3f3168d0ffce9aa595b713429ae897be2acb8ab8ed625754add124da24af4892ed1264aeba0d67bcaf61bb7a97b926a0eaa89e2ec263545f5bbbb57bbbb318e06d5445c5207ad5758dd90a379ed77a1766fbf2bf5bb4bf54bdb69a95f6faaaeeaa92820e0b082125fb07f60d226ed6704399ec4171fb011d9bca4a2d168486c9e6391cdd39824f9487a5212f2b34bcbc4d138256e89a37149d4b6c2855a2dbea8918873181107720d215761dec2fbd4414d2e0bf0ac466f2cc5534d4f3528db0dca26a76e4f55717625d5534d1467711667ccccf49d6dec7cab41f5d6a06ab58621ca6f2cb2ac76a6ca906b928a8be345a0d715eaffc31e7f8f742c11a2843524b44087159fa73a2650198182540a3cdedb9a29ab184cc5430bfe50c51771fb09d69446e9392c32e159aa75b012cc826766e70ac04873e6d9a396672130b19f96dd91770929e6b5c190e44a85abe7f90cc3d233cf330c49cfde4b2d23eca7feabcadfb073c48aafd88506014bb3115ad451222ac722512dcd46103ac12ac98cbd0069c302a8598ed5b1646a3642f7987dcc8065456f273cfa68c46d8dd5ac635e0e338eedc3c04d0d88486235f3d684126c402c6b1817eb49a1c755ec07035c0ecf8819175229a3ad86274db319f38c98b194ae9123478e6c60edb4995ba8776d5172433cca435b5f2716891823eff0280f35d75c732c0604d189f763d402636cbd47ef8fe4e268e2fd3cb31783d8bd13afd777feface43ef3983c8823d830c68f01a45e46966096e146f779f5ecc80f5ee25992548bb62a426d2a7d7d88b3d567609bebef04bb1b29b6326f8d746c9cc342b1ec9800d5bf2f05043f9cb31d6bffca1213df6acaf6e90be1d2a4b8ebb94a6f111a2c22a50f9e32d882a5ee47a3e96bd6e6ae27ce806cd87b4ccc79b9a2d1f7e6a7078efe34d4d945fffeaf7ddff4430ded6387dbcada925200a11627573b3c4dddded8a2cd885b444065ee136656577bad40ed63d58a751c3d3213f4ac5173be51088ee31b3144f49efc8532cdb95daa18a65988e20397395450a1c26179a403242820c67a2bce8f343b540248af1abe5d73588749d2ea90c8614cd54d8b59c9a99f335ccc4d99e3534d0c1273d2e2eefd0a04e6a18e31789281f603e942f2d1ae88087a8835bb5a14cf632f45a63de41d25b2e59b145d3a2ce947e745170a938e61d98a40c1339e7bb013f07ae14cbb6cc2e9b17fa5d70e74bf595fa0d9c80c49422270ce18686a31ae8cc80822a226494dcccd669d460904cf50ed63d58a7b15ec3aafe887b5b735b63c3bfb60801fc00000314277a785c1ecbdd8f66a01fcd80fc6856003f9ac9fc6876003f9a2140458a00c02e78f9d49dafe547b30cfc6816f3a3d900fc6846003f9a19c08f66418e80189552d70f108305c5aa09b298ac83d60d10733835f45f2f40ac88003121a50a34802f0a188bd150a18651ca830e5acf20d6c1ab841a462a15025021001f2509c2085e6f6a18ad960680d826f1a8212c86416c97e88e1aae52d3d32b4e0db7a906c551a9e1d67c7a7c7a7c900c4043f2b9bd90a8e1da765c3baef9cc5e184a65c48d1017ba9b91392dd5b1a9add8527550951cabd85e75d03a9d52c3b5fa655a18dc44cc1483d6998999826c8b7566622a227fe21a0fd5c05578a8b7c41b0b687e9dc3f0503311b3905f5f0ef242d8f4970fd90d0769a726962d33d55d671a27ad018e24a7d8e14520576ab15593d59056cd8a86232c78ada485e6840d12da64fa68f2b1048515c68bd4961b2aed76041d239c1c517504d5112d3e4ab51592b02d990046b26827283589e9ccb866ad4b942d4d34e87064cdab95866835014182e18a17a3b13aa51a80269b8cd0e1a9313521f96060651515446a8716858290385140dc10a1948aaa880956445670a1634971ad091124d28756eb614729891b25ac2e4d685d985c4181ed0a2672251745eb321e4083eaf782dfdb0a53bf9b0288fb75b71a80122d258f058a24ce269205e586a55a2498d8897199915cf4415a4a64762e48242c2593a3fbc2881fa37b0f162f562f46228ed2a51d1636357a739332f3ecbaee05aa33e0c79b113a548be257caef2ed59565a92ed364224f8d65d044365b3ece59a4eff5f1ed315c214e3c44f5f0420f4bbedd7ba0f3ed321f6ef8762114bebd480610dfce8111df37344d7c7b8d0e9abedd831fa47cffa0c4b78f604912104e94767cc75b1a1ebea99e7caf0c9e49b622f9fc0bd4c0150c6d5a50c1882496987506544d4cba582981052b9875d477d3f77677a7e0a1144757802035114397ab21408a9030f22608282198b4dc60f84ea1a66019d1ac7695b7bbb5a51709c9ba25d81359f4f459d79463b3b88ca4ae73118fdde3e83ce454c312264e6a509c64789a623c293bc61c9a4695da11023684965c48c20a446bd389233f61b8b0592def730efc31e25c2ec11f2330943e3befc0703af51fa3f5edf18bf4cb83090efe85f2ba5ba9835c2e70b3fc18ed961fa3dd2e3f467b63f98fd17ed92adb62bd8bdd90cff22977769cdb0137c986ecb7a95b1c6c9c0e9e29948876fc7d547e9fa474bb4b78196917df208e29d4c43abe8e7dddc3b4bacca3bebe6982ffea7c3871e9d99707871a7efb8a329a63553ecc39a77f3ee5bee69cf3357bd60ff67ad5eff59a5f7dddf89c04d93e3b78ea9c5796bdbef78f652f1c93beaa94f2bb580a09935272137c8137be29a532543e66aadc4eb454e0a68d539b1a8b4c9822ae8a084323fb7629625f0b94aca53f3a4efe903d65b70aa067b03c33c61e84684f6a47cd0ed8b1a0bcb9258a16976c21211cc85069f1c0cc755d2787cc930fab106466f8304ac91b1927290c6880468d25c3028bcc910af39e8f37322690a1ed60050424e3e9a6773a6a88813777eeeef40563b096e05c3004fdd2279882f78375f91facc316164bc13286a16220244374b6014acd0993254f55b62932387165bc858aaf1bf8279327319145f4137294bedcba7c74de92e5a3db4ec89973ca6d5a2614ea7641ea5c1d228b88c309385be5a3b3cbee6a8c9413148f9453113e8a58ea11430b43849b9d30351021a4ca7573b37d9bf3792c820892253718f6f74ab0eeb7bcdce64c8ddecddf31b6ef34ee8fc70f2b0f524a8e99dba95ef0f1e685cc921b7cf0720449ed3edebc3045f102c40b8f6f69d6a3a9cb95276476025397ee8d9c5a41cabcd5141f6f2b1419537b5cf0f156c4843834bb5bbe945336ef986edf96f39951e040c59bac50045126178cbb556af3f65133df8424dddb05b455a2a2d16849bbdc76afef52e4f021d077e18aef3c3eed146969f3eb204cd172f59dcbaedb12b10548bfbe2bb43070ce9d4f077f2210bff3196b662eba175d1b7c51ca18e5a987401f637168d6a28c7af41f0c700c3032e3403f9a751c98a349b8262461a486b741d38c81336bb29344263103ea9c3973e6e4c0a482e9f4c39c81cd4e4f202bc4179ece24dd01311147d4f97a7cd16a270ea2cec59a8983a8bf16c6825d24b6a04ec1361259500f2f60aeebc0ee7b11a99d6b839eaf11a64813d5dbe85660430f3c72225111f72392f0d4c3d9c4ab20e600336205f407572fb5da06d5441c150f2d1b7f3555eab36aae20c210817ebb08b0219e823d238a2095037338e06a07da6c041e189468331e5da7ceacc9d27e72ce8fe9d7b1fa4ad4a79b2a1e7239e59e9efabc4d2b187cc279f530a13935a59e4a25a1a46d914d910595b40ea2ce32d450cea13d758ea186120a2abed8a72ea9448ac5070c6bd4c3e498bdea20ea3d45d470d6b867760cf69ef7bd885497ab46e713aa33c0f9ac7510ed251e7f11a92dd7063d9fa34918b9b4b0430c6f8343437b968fe0819935d9710618e19638261eaa4e7d4a8944928718449d5aa95c9e7a8c92871c9ecaab324f7d5ecd5a7cd17aeab386f8a23ea55cd2539f53d4299d95529768a28c23dd3c956c9e7a4f1095070ca90f0faf550751a7b45618cc6a82a1508361d1e421a29ef050e8c3b59aafede979eade8b480d67ad023d1e4a671fe7016bb08c7eb1102010dfa8070ca15f2fd475092641e13c1ed2cf8965d4f3681c96c9a8d0c7596bf15313a170eadcc4e3d4d78a3aa5b5c260eea18c7a74796514b159d541d47962930dcba86c94d60a8339a5bed343e50a720d2ca3de93a552efa5262aa2d57d622a90d4c8e66fc8afc1432ea75ed4444c6b05c3ca03d2e75a13c91834fd837ad9662fcdda4eacb9d644bdc441d429ad94775410023dff62aaeced047613cba8c79a8965b405f65264417dd6c25e7aeab3d694c69a273f9cb5f8b36614d95096b54f3aa19ece5ac7fd70d69e86d014fc80ab156089b8137703cea5943bc4a531d672dffd42ed9e02fb1454314375a7e1b216c77c299b98dfcb8f5b700533e8422e2f732eb4e2e50a9649172c932b5e7a18e365517de92be49c18149f8324e2c4a0f8dc058f633e73a7f1388ee338aec8398ee3b8b07e9c33b62e5b3a37e38c5209d7dd8d84f3384f88d5667ae9a4dc80181ba464e841464d0fb336a2c410689c90004409f6b18595c3001a0616abeb28dd265996c82c4e2e48faf1b5e02925912609a91bab866ee70fed79155cfd7e85f150f882ed070bc2a2acef723f1e5af652d929d2b7ecab292bbed799d7190bf0ec73ef4544aa897ad656553be51de9b4f9f60e441839b6057bbb0d103cf01f097cf01bf43c2bd57d72da16dc2ef9aa83965a2c66e2259609611947169c426415409e2e753daefb56ea734da0438875bec1c84d1ee1069532c79cf3a5097e7a287fd24929083675482cef32fbf26e8cae1096b9999b631369e314c72647855f5243d9af2783c9d663c898c03a0d0cacdb7e1d683dc5be304853c3cfaa89e82f80228b088351da9e418f6e96c1b2e892e33830e429a3424530970b8335e819740b1ec7c85152edb653272a70a9915d2704eb888e5b38540f85b95b0bd74e7b0531b3ccb6532c5470583840ac9086cc53b77b0404892438465c116288a459fb00c587314e36b0404390eec1cb0cd332c3755054466534d2d6dde80e1b333f91c284c422a58ef782d52aa38578b46b744a12d69417e894132696294066e7f113385a9ee1dce0d800878919595932ca3927afd2fa605974ae52d779ce39e776e47a8c8e2f887d233765f7721c578f484195e206478857001fbafc608b12a5e3cbeca884208864d9e2c3132f50683ea276b089fa01b5548b0f2df4860453f3f0c31b245494a849a12a4e6ca28a882e63ae30d17cc4b8012ec6c8c58bfb23a6c05e5258e2bb0509d74e69296069915324ad089cab37dddddd9ec283f2d2ca610cab1b4b0e692896198e60c96e16541ca538363cbb8c382d507d1f6f7190e23c75489ce9d16343cf198f0996f80f989d946166ad2f989452260463d9ce2029a55218b784514ac887716a877c18afe404c15e59856777395572dc3c7b0772e4aca9322aeff3f12627c98c23156ddd6e85b1ecc8e98b54122fdc88706912e73cd57962824a52123da6a8ba213a4a81b0c00410573d78799a35193060aae600a192e44c0b563b5df456e08468a76638522989aa12041147c090e6052b2d48499a618d0d6654e098ab94e95d7b76777727e3a0bb9db6d776d81b9e5dea8a98ac5b288925ac4313575a80450a9014489913068732404c997a93a5559084528a3735394b3cfd784b22860f6543bca2dd8fbe106c9dfe38408db75b1c9a446749686459e7323a767f80835f8222f4272141e7277e48ca6618ed618c31ded8ef70f027e93a974164831d63c7eee1c518e30d7e1cfcd1b745649915112f365113b9df847319bb612437caa6d17cc6c0821b83582260d5486a1242d17f80e38f66d40b61eb3c831c12f51e4d42287a1cad00ce654c3a0d4708a5044e6c8c91e74e8322f077dd49d9cdd94404feaefb952940d91581b96dc1eeed42607fb9e7b885119f86a07f7d727fe77d637f1ac9fd703bb99dff349ab1e5943b33b2e029e7ec25bc68348d248dc6734ec92c39eed9062f36916c9e712c7e538921491bdbac63de1b0d6c500235eb4fc672f49b2255b3113c1a72309298da6c84183118093293deb11c4b9b8d10bbcc24d8844d181315477fcb61b1c8419ed3586419f50b50bc4ec47c623d553a8f748f158bee71b1d895603ee7f45f6c322db14af0ab665becc4bada7f31eec9492824c173ee9120d6be139bef8aedd2d3988cb95c15f8bed50243137c8ce0f7d4431b5f68e3fbce2348c1d81161e0d1b9e69dd004cff9ae1ad78cd8c52acbbc97e0aad91618c42407c5a48c6ca277bb66349b47c13f415e440210b5be8b22d68db13821ec6347515f7c422ba11fbf907121ea4797fef8858c0c3d426e759048d513914846efe4e2cf4f29a53453591fa376f0f061b43510b5fb18b563876729a3767461e243878d84247eec70439a1b535398d41031456748fb60fbc2f800c145099bb98569513bb8bc3e46eda8fd0b4a23243df74dfa4976f95aa1ee6763afaaa4df5e4d991505402cdb9e3e272827b736563c93f1382459d6400d04d432787bc8f9aac99eddcdc1d17b734e191db412f4d086ec6be0d816eb400f7eec8f9e03fb20289d9013fd55c3d1794f89a37be6666666e6a2a28f5e54821781e68cdc94dd41ddafcab2fdb8ed8fe3e6765ca572fa8b52184c760d7e2f8e6b9e3b92997ef3e7ae8cccfa1e1dd779ec0583510ad6d7d70d7321d4715e6924a271ba90605196ddd88a9105ab0700bb92e970f2ab924a90b2ac05a083f449bf3959cd8c18ca97dbd1199265dddc273be79c7633c6ed1d6eb240e35545484a6943e87783889d2f483b191dc4711c7b005e5d0ce6208c650cebbce561d7759dd7a26dd1751dac76d459b1d8750bf8900371ac9014a51d114581376784c1fc6246de167148ca86c12f9d97a594b2999b8dfc525b1fa3760ce939270db5e763d40ea41ef5fb18a5e38ad754d7c7281d6daaf49cb3d3d49d8f513a7e00b21ac282476977c3605edf16b18e06e48c73ce094a8eeb6d119dcb70bb32ec4e094a221646279da7a97627e88ea26b16a1944a2a297768d2c78242430bc292fa75109494f9f5281d5232905134237dab731b924bf5aced5cf02250486db5488852995213edd506b553ba5736107889f6d19d916ab857539a16465b6d8ba5ee2a1d54668d4464d1bec4e5c3a974e49b675c5b2d0c0eecab4622be58b097882cda2fc0b31628ab949a48c28941edad9756dba21dc9e4f13d952413df53e95bd29a0c8944dc540bdf1c926faec93727c5ca735cbeb91cbe3ba80e6a87d1d8297d7749b3b9a96fef25fa6a61c434dc517d7b57a786b2eabb25a826eaa862507b15b5faf6eeeadb690d3ba82e4d64d15d53c7c4b2f6b04bf3edddd2b7cbaa26a2b456186cc170a5aabe5df288443862d0d5c2e8406cc1441caa1387b6e39c5f0f1905df4b7cafcf98e5c3f531c583c80204966d15cf685f16239619041b2c8b23bc9ed4751cfbcd810e4297fd9c73ced9dd2ddba71c92cd0c63ce9eed0de2a01406631d4c987c2fa69499999999b95f10a815067bbdfcc57cab0cba60593ba96dab51ca5a3bcbda35a317268096c19c22854f931aa620aaf1057be7d3bb988a13aa741529d4199cb77fee792a80d496bb58df812e154aa61f7d651905919c764021dfc0155b8f31c6096478d8812145928146b4fd6c8551ea67c87aba2e096c120f753e17e9a7535f241ea23e777c48c116f801cbe524605fda77eb23d646df07497e7244dbac049f97c0f3a53d8db32fc6eec5b683a6bb622074d0f42fa662490d21d6f90fa6ab58aa3ed7bf559a4f3c761527d476154a75864797af02480de54bd687a17c30556aeffa08e6862d3b3eec400a7e9084042d8fb6e75c06eff8f8e197836704598585052db326fc63f47d90a4042c8fb6a73d9a84a0c17fd08a253509418375d606d1b64a4c43cf397fd4e1f1a456380d4567cd0d74e8d0998189a8ae8f373a2e7c280474021d203cea893926d49d373baab7462e55eac3914ac94ca77ac206a7a29841ea68a2085413495a6acfc7db9c16483273aa2696395352739e96b04d406e7bbfce3de7a452793ededea0f950e8f6260c5fbdd9f2ebfca5be84e63475500a0c3ede8ea0f3f24398adbbbb239a97b248ce29a57c49067af68065cd1c60593f77777777472c1d2985c1b8bbf91b0c4390dbc382176eecc7e8c6cec7047c04d7a59452b6bbbe37b82fc03378e36771cc971ccaa3e92c39db8fa4117b57ee6fe0989f8265f1c788bf1bf219dc7de37931323348c3d807fdbae775b794ed452c8b2ee9773f63f863b43fbf9f9d7987dda5b2398e4a17db22767777cbbabb2b7211c6f8c801d33a440a18afd816b1bbdb394ed228dbfbc3a36f3006cba2bbd6781128dcc8cd75b4e57d3b3c3c95524a25ab250b6b3c27a3acf3bacdc1922b3e6fa7877e2eae737d45266bd2094adaea58b4fa7c2ecef515a1563a345c34ea6ff77c7c4e7cdcdc95ed9c27e42a795c94e66c6eaea3adcf73edecb866b4f37c2b5e2e62b03a584a5dd6510946564ca8035778b3e532eaba2572e0353bacca581d7c67e4cf8151c641eccca2741af58b48ed7a602b62b8479675100b1d88e24a95c938596c31a7f47abe39244fb866b443a0e7652b84a2accef17cbe183c2495865009a34274e9ae108d464bdadddddd5d25d0fcee2ee5982081891c7efdc6441018918d5462889f73892a301ab385f10f57b480ca15906faf6f60b5ef2bab6f9729518596f1a6c79701a052a8d46d37453974886a0000080093150020200c0c06040271402c1c4982b47714000d6b8a3c745a3817c963618c8230886120c6180080018410830c4086a11bea00b7b6d100ecb5d41b9e69773df172174da068e6612b42dca2455097869603fe2bce9987a4d962c34dd8a98cc20013ea3163bccd48bc08978aef066ca02f5b558ddae90b7f8efb19e57d01b66df5a393d9430fbaabc25f040562a93a3a47d0ce9784276040b34328a845d478fd0e316149862f26aadc9f4e3e52f10eb64f6e64a3896985b6ca42df145a8be8ab4a9c2453ff6e48b8782da7597c7581d45bfee54f4fbdc8103ca3ec6bab8679db029646aa6ed2521e6312f4fcc5b1624e3b834c125e60b4c43160483fff9edba71b597097917152bd05127243f5972a2d0244262935f49fca9b1d84359c4e4bad80eabf0ba8cfbc6f6136cf7b7f3db9a73426702196784bff1443e4377bb42db5f2acf3a411cc32eef8ce5383ec39d226d61e0cd406760dd5486c82128cd6d0621ad4cd41ba33d542cbb0d2084c0962a1e6ac174d18f115bbb60fc94865668e6b7ba63fc519ac973d1daaed035ce000c918c02a5ba04f3347bfa7d9792daf5dc5d402077e3e3c2f9ccde1cfb894567fee2a9a422cdb88de3150b6fe1c8e79949ddb3b63ca39d6caf54f8302c291152314f96dc985892020fa98ae290ed1b8c4010abe645028609ffb12aca734175d9c801b5f4c4859944846244de48336a91193631e792959f2d7350f6c12d28911f187a78e9eb7d7aa66a01883043c6a25bc60421d3bc1f907f93730784a3fe9dc9a97b1db24fd7f8e455d58ef603804f7933c4b2290090cb8359db3aec3656b62f1493425845532f730ea88c250d76388c060a8b50425661428e8592ebb2541bfa963419b8791bb150a6e0443a4a379f234a90ae8416ccc5c24d00881d8cebb22f0094ac1836af7a532138083eae37e7971a1e12beba799f7b731921ac19ef3f5327b0040d01f19c7df84cfb05d33b9c45080d3fe6ef8c5d6265e4aac1e709ad1ced81642045264895884c5f242502b706bf2e85df22a2a7f74f06286460efe66b0244db37e60c043d6a80f705963fdd22a53bfddea0b0003a1ef6c59a2ac14c52a523a771a80b16fe3da904bb04dc89f676d120a9711c56e02cbea80f669360603ee6e5add3c71c010424eac04a990e000da1bd4aec0aa9a333ee33f558fd2ce87c6b26bce074b70bf0138431b69e53e8ef15f04a6c18d3544981e84428710ecdcb7e00ec7946708d1c0bdf52a4e551c39838aaaa29164690f3408a5df4a8a84ac74977b93bc7e5897526b35808664691a44c81ba42111d2f9dbc07d9266e7ac7af9451461a540c98f50346c6f33db9edd9a27057dfcfb3dd4a821cbee1d1245e48b0e6d87b019bb3bc7d65a0ef8a81cc80b85626698188f62bd586c8b41aa3acbca6b25b46870a8c12c081e72de711d1f34626609aa095ee75995fca063828b3a9aefa0dd96bcc1764e51f30b9e40addc69bfc4e9519c08f7720cb582e6b2383b8fab5885e7fe482520edb15bf8c2d88b368437c3c64cc2f4995c55b06010d3d00fea67170cd9916c5bd7ea935c1aa754854235e8786e7f3eb99389f8f1ff051ea03f7db2ad0066060fe0a0748ce7915d83e25512874901b6e975783cedf31cba0fd731d957fe1c4f40ef766ca10ec143bde0623deccc2b870d998ddeda5ab6b6dc7bd176e9c62948d38e09c01cbf5caacc0ee79b0c1615f2cef981ee77bb5a81d939d30aacb59ece0e6b1ae9c4d256d38839d7deead2d7a4cf735dd00e07a25f21300366fe28bad09633ce5ecb234494c3f85ceca223afdb8350b12720a6e841b25f59bf04b34e0947b7aef475c924583428fe6fcd60e48c5d71f0e32951ae2a66d487443a5dc9e3d366f3740995351968cc7c06bf8a6c917f7fa17655c3004d321bcbe240fd1ec4e7c5a995a4a2b2b276821c0949d48e30b57c150b47e3c0802388d91bd2d799cdc689270f99b59f88cc8329fc811a4780810896a050ae3a61b74502aa9ae6f7268a3237353788423c9f6ab6a5526649f60df0d183c192f04c3dceb78494a4f4ff1012e71e89d7816c18b7d42bcfb91ed32e287ae1c8b7221c0680521c40b4e9c6e9a4840d007a8cb90a177b9d347801e2431fe1b2314bfe009f4b7bb159437a65c0263cbbfdce5b1997a93cded6b2996305d3d7b1f0c396200aa74d48200cf4656d0f2c202f409bf1397faa0ef4ab448f86cafa48ecc4e4783580aca8ce992382a6e1831829a556517f8e7da9514c0c5136c870de812204590b27a792b2a84582fb0f1a76dae1226df4caf91e50e6d37e01ad5b4e8837c05fab88462044a603b10bc9e7d467cc641c89284e253c08eb7e3e307cf993811f971810e058557baf773bc74a3c20e9ec282d6514aef1ae2490d008926b691c111a5160ab9d5ab87a050d2d424d0bcad7eb3d2a9286c17f06692dc41e6e4206bfb15fd123067fda0c9d95949e80327c4dfe34b52245e1a9c3a8fef79c4bd04f89b4881b16c18304e9fba90d48b3f91c23119340bb793731d25d2cb4b9d9f24e03a8ff913c0dda860f1b84777207720acfed0a21b18bf244d77d50cd5fac921440afaef39403328acc9b0ced0a3f3afbd69608e7728c885898217e1ef20c4573e99f2882bd2a1a32143c37c4bc065331cca0bcabd490e06cc5ffaf0eec9f335810cbfd2f994623f1841d00d89b1161cdfff582741543e6b5948ce980bfd346af7cf525254b653020284032a8ea584bba7878d5292311b0d47aba3fd9ca8dda52acc323f3cba39a25b871e2ab1953eec143712e59f5a1158809ec7717b4952b27b76773e63a93589be13d74bbd82adae4722efdfa3f4ec3023336201ea2daeaace3143d4387b597456350f6fd56b1ad25512394481b6e9e1a85da688753e4dfc0fbb25ac3583d15e0409d4aea270886e1cdc870f88cad20a987b8b4428b9039f365ba8060e36526936aed8d1576082e8e10ca7a7b619ca093af61c110136f482ade894f2b8f6dfa9c15117b5d7871a68801000862e71cd0029a8a87fad7a9a146dcaade1efe4f7e689daefc6cdbd7c639fd9808d77909f03b10b47a936bd0bce7c2378559dfe97e736fd7a73036d22dba7ee0c72d0db17f4d9a12d56d4eb711cd81153ccf7979e300332b111d206eb85ffbb6e31131d60940de27e4cb6c88a3b0bdc79036e9a7f145fb19e30dc1ee02dcda6ab3074d97a89061f0e56347c703f5a92dedaee295687e78b4a771c47a9037e22b14d3fd09deaf7342f220990111fc8621461c7078f8e3852919461765b95549da3dbdc66ba644c42458b4c2f4d9ea53349f2d9adff38a6473c557633f160b30332c3fe8ecb362a60d8052439603f5a57bc1a08d87a5f430856b2a7d7673b1e1338fb66b3dc00a06572a19fc9ba9c90fd276bf4f12905781f118d0e4cfcd5a0266791849e99f919e7f91f5219c03098d23e0fcc78875b49756bdc49f357b6bf355fcda9903f040885b2015a248a498ada103009f3ebdde221badd73b6ce7c482a74619fac0786a2c481e835ab171b9cc6fd3eef8b5704bf0dbd41102810592b3a6c0c81ba30579d411f3736402bb6d2c75479bb240a959d55fe67eef343b50dc35ab21e0188f4f006b937b63ea647cdb126f200e29e10382c300c4a7cca776a5fd124d23ff7513a5d62e407918e4f4ada425f0f9ef38b09f77b496d5bd815311397436dec4d888c6591618123fa20e9a41e0ddf592a06992e5334e56de36cd38acd24ed14d7019a4847848302e769aec4a749f70e11a9b95f6546d37550e986ee5a9017f3c2e7ade1a33ad27258c0472af7dc620ccc238ca958eca257800bd2dbb8e898c2749c9d63ff88cd37ccf50b8f867e092173b02937cb6e14e247d1f839e2a9fc875a212c1188f266c98e4515ba860a6a29f57a98f675dc3df7d84294ddfc767c78df660a1a2a2e0824ef0a3004001e2011fda0d7c53c8dbabee53fa971715e48351892b98c4db9b4571bb28965801402a29f6621de8ba316aafbab37a16103fb7e9b29defc3d4c2981f3121a0b0a208aa09567ca5806c435fd52b9f491f8b397f8f0cbe0177e476917440ee40495d2f9ad22ba9f173f178b88b92f340d845cac75a23797c2ce0b6fdaab3f8f4d64612e51584d3960f09dc4f71bfff48b00740dd3ab9cb90b4764fb2bda6e0f3aa5b282374272608a136749d09c18f232cb61fb07ddd68f6f60bb5bc449cbe1832c2eefa3d67e26a5453aeddddd65eb48863409e5af0924e1b7854ba35e418d1eb32753a44d4dded7db6784918ead81ae514a208237d67124dd393136ee8bdaf0e0ec7020729b272914af4ed2a1f6801d46a61c922619b63ee3d1a77d39b08c4907ca98a42497a300d3bdd85eb4971901df2e5a4c75bd05a638da928e0a5f7b7c559f37245bfed60ee2cbc971201b1eb5fd702c656713d7b0d2f3cb6575dcd7c5eeeb1242d635307eadb8153dcc038787a348a66355e3558dafa340739d2ad26f636f406b9e073ec87148976f04716c2fe39166d91085139685aa4ab8122f84d8ea3fa415a1a631696017f35b5df023a621b11fe09f2e078126094b9df7059271b88319f84ea5a95f94f05b8cddb8e84e0e26aafcddf22ee22d3137afa9abef9265a1e4642846d1b6b1ff3f7eac250c1e8e2a89923e66997f385d76063afbadf13fa505190d2d22c6bf62617c5685ccc136271a9ebcb739e6026805372cd8a38ac7f5aa37b037650a508867b1ca8e1db18603649603d5096554eac5efca5955fdaff67ca81855aad13d84fb4dbe4f5b5a279b18dbdbe70463f830bc916b3d3cb0cfa664b2386a7697b47eb4e9e2bb1c24d48004e8494fbcc44912ac3d4940dac86730b429402e6562b1a12ec192c9a13d041acfbc79644519ca676ff8cfe6dec13a2c59cca1c171b8606bdd730db32fabbb592a63dbc0a2527c784fbb3adf60dac6f567b7e563c2382d87ab533afda78393ffc00a4fb155bb6f806c3b3294e0edf8ebbad00911a28c4f95a6ba894c57f4128b5494dc8cf38693fb48d1771a419c910b0471659a94223e6a62079f821a4fbd7b0046628ce9cc0e5c4f3eb7d355d6af1d465d41921289239ca063cfe18ca6ae1ca99c02a5a323b173510a12d98adce580697755f4f33051d8aad6122528de8c67b72d7c9d4e30372a7e441c28614afc753cacfa2dccb2c7dfccf1974aa7ad71cac631c137c49023873debf7349b11577039c05fb4574f1604426103d14c4d3d23539581096e17fd740f5d149b09ed83877894790f038c6a16629a25af25ad30c85cd98955b01f9833780cb73a7b972f301185be42e59cc3ce3a7feca9be87f2e6c5e1d8296a5a75ed1a280811efde7969023dd82358e7b31e5cb3d73f2272793e665bfb3fa89eba63912204824c4108544c2b0b6ae4034706efc843cdbf2cf65f8eec45b323664380259671a4287d956041c328e938e73d9ec9c93f0dba02463b761a5d425d2691db71947e761f4a688fd18f063a128ed6f61e04d679fbb764c4f3dc37c5adf71cc824df203468639bb24eed79030e48374f46807f29b126907b824f9ff6f3ee8bce4ac7d65d7f58afcf982708a662188d7e8392a945877d29e1df7d1b3a8e69046f8a36035600bc3a5931c74bb70c4982650b4c09f514eecc0cc04ee81a5d77993d5bb4555f976644020a86d951bd9d74eb94baf442a74dbb77474fbd1f1d3994ae2db7b37cafdcbb2b9c0367a13a271c9b006bc69c1d4f55d220cee00ee20fa4602e38ce89f16644e13e10b2457e990ffbb5511f754302dea986c0b95f85223217657ada106987fc86b24bc0495f6d32043b5c0479d0a2f128b077f0ecabe77172e6aa537a92d52b38366df46a90d9ebe1dbf49431473d9351ea6bf844f730c73b4b1abe4a61348da5cc92f344f6593691b186ec02252256dd785da5a1d79978f597a8c015772df8b3cc71f23aab97f554e71df96f0bb9acf58d39275fb94bd2b6da73a167c3d988a4005562992e180f8c8805e9e84efe1d990721b41517991cc221b4183fd2ae0ada483032df48f1a63c0fdc426ff590001510c74a709628aa2f23564e6a8e3f707f7448c0806c230ec8043c43d8710291cac2a853c81ab7a89443c85c0fa2b9ac2a3655950831918acb40d224d6cae3c664692d61c191dd02dbc7612ebaae17897349c86a44a80f1d6002c404c907c3a4d6908cbefbab3f4ba77dd39d8a35f28efc93a051d9863e4f6b5956c9e22829fad2665af18b728fc4ac52b97042b18f350a0fcc10a58a7a3011dce423e25937c5701cbb2251e8d3480e4ef075438880214307983ab156aa4b1e9624471b5f73b15d58521e7942f1616c915f029f1f531c50f3e2a49348888f484817a00d65c88e6f6cc7d31d951830c214bda4ae32253c02bbb1ccb090f20cdf1a8c38d23b2fc4e5404df37d8c64d6e2ebe485f9df0e28177ea949ba3db15760a1aeb753199413a9451eb83d1142ab71fb44730979245ff5a5dfa4bd218fc322a8b3b83f831e7a06cc6103a903517446253cc2b7a116d8c5fd19faf90bb12550ee79286e0efeb27030fb325feedfaf6865a70b6d5da974bffbc3a57e93575b512d627a51fb421902d2438be76358ce90abed653027bc062dc9a2c65320ba76ed8ef3de17f642b849d76fd70867fbf802ceac927e8df84517d457f2ad011d281b348e419ae00fae3505e08587ad20b6c3347e8e16e3bb8bde49213797db16d60eb1ee4de338a7bcc8f686a13327ce9e2160cb9d912f772ca8141c238f2ec2bcf707ee19da33c0dc95a428cfe82186b75028cd71b522adef0dda1d4388d111de125fe63ab9bf44c32134cc062307b4ba6aeccf1ba8a02da1fa57706f860b68377482ec807c1c0093f5c41357488741556491dad9fa4f56b215190f268402fa6a932ba452cf253cd2c7d242f5c487d6ed7ef059e7b0733bdb35b7df17b43d930be507366e068e853a73e086b2b44c14922fc9051670a8822c4b331f877f6a55ae7be5478d92f159a8f5f3022a885c3212c13e226f2b19810bdae779a1f3df78279c5e7dc6fef723b886132a9cacb2e90eb43b18f9dc17dd74942f53bc1276bbcec8829972a700c12fe3706ca4274389f1c001e11f1c7c00ea880c2f401b31831ad45cf9a5c8844353296fe6d6a86472525ecca01dbf122b76431b08734b59010d5d3962b4d008800ea29950cda4902cc248c6c162b8011500f885135996f20e8a8c7c9b8082044585606517147179c27bd9ddbe9fb09428ff2f65e098e78c949f717151a40dc548d893eb806b8b068c5325a15fc3595cd0b9500e92a10be064f6067daf51382704ed964b7735064c01e02b3f018bf33327d984cfae63c71fecd3309b848b55e46ef4fc172927e86f094c88cc71c94e98f430cebf05a0803926f49bd0b74152b784768dad1972e97198547555b5baa118d982cb53252ed013cb8a584b96fc43f88fef9cfc38bd7726f095945f9d6c86ff267bd494ece14a84001c8ce0801ec321907b421d362d55b6f9a8dac00c402837db542a702613e6cad4ae948d05815d7017c4838300bc4cb24aca8f3612425322bb298c6424860abb768f3a9a5acd061170a3c53c952981f61742181b5d87980d6b2e253832e144077b0936457dd6b2fb1b5e8f53877699700f684cc31742915aaf7d7d7a3cab488ee8c70daad90b0ce64516be338a3dfd5bfae4f74b84c6c475c84cb93548c4a32c540f703adbd4666720970127b8398eebe44fe3f7b5fc20315b2d7734704f92a4a9f8d814e4505d291fe74e527ee665b5b72719d324af3f01c9f1408c28f917e94cec4aecb2189f3bd3ddea6a93a8bcd77d20511877989d08ab5ae7c7665d51e050629904b737b9e9421576e58ff81cef5b32e30ded9dce163a029a7682972e6b5e1dee7246f666986ce8a550e98948626f5803bfdc3757760e01b4bc9b85fe2cf4783fbef29e32c2f98cce1668438b265971ceee6249695782e6d632b9533eb266cae4bf53692b6c5f15caf52d17ca3af6793f5f0373d885893e6197c80852a5d0c47d43d3d4b80340336660bdd3a40e4410e776322ec026adb7261947907e80b487892b505a3a2dbc60325ad3f346bbad0188b73dd5273ceafa08506ffeb9348a6f14dcd46affb0f0c9232cdfea254a3a4f6a9031fb2a8654c907435d4d7195190701b5a4c292940b189e18cde725a918d415e0c5108ec442f62e31fe23154d95b8b91aca61f6efdf2fc38bee56b525098284646019c0e388269c7113a4d4d62ae6506f3cec603db46d047937368e8a16a9a5ba33ce9d3ddf4e0c59ed944aa36666b4896a94af7926ab91e149ea93105a9b926222acdd43994c37615bf28c83226d46f858956941ccd3ec0811a1b5aed85970319e04318c079ab5dbfd9d64892f5f33a8e273af47984627d6059d15364119a78c58afcfc9b526ab79e0ada22e35082cada9c9e2b7fdc2cc013a2e9d12a9c6b5713b671f0223c1c995fb6000206194e50fcb1e319c91253742f5aacb5904057b303e97477db190923e9dd62afbacd0626d8dd80bc16934b2eb04e5e257e596b81c98ceea74d37f1687e6d49d64aacd0eb725fbc1eacc0ea578b4a911d882e6705a7589f3e5ab8c5e679b65691b8e9f2d40bb06a9ba155c8c7b0b92b37cdc23343ce38dd15fa71c459faef5f8106b21669e834d8fd1c4b93207b5df4eeee17583e4e5d9c9f587b2e10953944bc48aaed5db025d7f875511433d169e4d8c98d5adbc290b96c2dc5bac86da1fa9d9d061431282b8584c07102940d4d696af573ab353df5a7048af52497564558558443dbb9a22afcb78ccc5340ab677ef105df495bd590dbac04e912d75997132fa0c2347dab45757cddbd4a63ab5e68613ceab69a5014bdf04aaa10cedaed1a1b1a1c5b9215d917135461a2b53202f6db874f58d61ba9a445a49daf8e734509c428eaada329a78961823d961eb2bf3caae7e6d82ea4444bc7095383ed589fd218a0bb01e6f80e4ba3ff4ec0b2f277ed20474aae6f2a8e1bfe726575613eb06a5bd6c3daf96a3a11617fcf0330328b9f1134e4f90956e5a7d2bd9a81b10f29554071e134d5c536d3aa82077c4c2059efc278d1fe1d89daf0957db9016dcc30683f2aa4a316df944d8c250d18d0e6f2ce99aed1dd3a13beb09af91b6c44d02aba8ec3745caaa51c00489ed1eae142014f8c31f72370a5d84b72d7a36d4b7d2c29e5a0d21a6f108d6dc8a32243b1cc6646ee0733afa2c25321de4e6bc5bb09638f0243d69b40dac99a84a07d774392e803a1cf65284e74a1c559cee7ba64ce7dc47a54bcb68030da9e80b4eb0dff206a6800aaf24db1144e7839fdc127aa33fda3aa5b88f83c01f120bf6fa13f868b0ed5ee8b53b2304f75732e69388804c12936cfc74eca0bef49d01fd7573ee73c75530d2648544313ce44a0b1d3af490f93422b16366913aad05acd163ea1db84ac1fde8554ab8bde8a413166d5bd8503300a25576c0339a35401c90d55b51eb25c38895c519312125b8399faef9ac61d6c881c3f718a013df768916294e6767ffab4d1953406a9a4347f0e91c185bda44ac08fe1cf2dd07f6ed64b44240f26bab65089a1e89a0c34247594c8a645981ee0e03c9e4b4254cee4e10d9051de4630befb5ae2cf3c08ec334f1c2beb5a52a8e92ecb8337b032b387fbc854e47f1866720f19a378399511d304e9d4247b18bff8d13413dccd55bd8083cd05d900828df1c9d39b46575e68650844ff834675a557fed5f7ed7ddfe2f0f1a69a46cac0e1d4603ba1da7eec2f253ab6aaf65cacb664889125c76074d5930a400d9f07696ff408c2c018b8d10431581b12c4440bfffcbb2279d3b3f081f780081dc01d409d633fa6dbb706640cf335706e5684572c702b78cb8efc5c84c270190759a1325d117c8aa33e3af6e07a08f17cee4174d5911102e6383bf59b8cce2ce2cf18faf81aa655e80a812d1ab119bc099a9a88fbbbae577bbbdcb8443c2c9168f8d823c58827dbd2b2c84090e1d3e10b3dc260ca84b5890c443c93b954914d67dbc72a818157594085b7a08f324f3cdc94152124a25435afc37039d33b7d3fa7c3672232249d806d1a9e62709117a0a892967e38036f3be574296d60bc60131b123666adcfe9e98bffa33e57e238e0b76cffc8f136c1e1b2648a68c42ba40a76ddbc1f0bca24f2c594cdfa8500dab47fa842018847eae7d2300fce6d66fed3e5288df9ffddce8e2a9b33c292e4d0323a78a448a4b209f64c266593063e0583a35bac448a85db5a0f6ab1cf512971b4238794801829a2ffbc8804b8c08f6b0dbcd50bb38b2a250577d66fed9d622095b4ad7b2efb2f280d7272de12631a31c3d51b37f65b72d393517b73b1809094653b58d3d1b296e01584b608376982416833c87148da2bcd4e9da01504ad2ba1538b90de518333dedf7db23bf5f6d20230483a58e53a34bc30cf691b4a542caf817bfc8dc0338fff675a38509c5a85056b1bfabfd67b4787d35f9901a756dd2c5eb51a02ef36577f0e2ec83bf08c7ead5a6b5ef9c05feb030a3ecf6b3e938e06b565da15be7bf8f1cba981f1c1ea559de963e93b1d8c67009f122b01c6739e16022008a8d6c3ca5d0c3367e4acf216a20af83af46cc4cc54afd54c358155d00fa3b9959bce398a3f2c41b94a4ae6efab088c410e6573d2317d2525f419ba908639bdea01554102961f38296014067703fc3ab36d24d2500e2f53f4a849f60ddcd41f21acff4b6acb2471dbe4016acccd74342e5f783a04db2d6bbeb07b8d7914853cbfbbc2de7a3e40693a476717b2410344f12d68c3eee6a3ec22adfab7b85ce6c58948c46858c4c1769747fe545d574e39f676e064249c2b2d20574c3cbca07b8ed8e5240f7c1e11cf1cfd3813a3dd02f907018b2adb91b967c2e01cb0192b4148aa290acf5b9b5fdf16d3029231149ecb82d92c2a3ca32b737885915a52135280f42a768ff5478dd79a3102958567265f1e3ba211906a44d737cba81b814b35f855caddfb0fa7f8231ef307c406331bfecc15c9d333de281820bed1c950d520e190462150192415c97de40bf0891c2ea8dc80604466e4c630d34175c20753e782be100a086329c989ed255ae614a2deefc77a2cb1ced9a7d6581f75d7fe0e2a8cd8f50169a422fc000fb97e1aa3670b6b56bad5b079ba85e82afd4a40d460468d60d01624ca03be44e19e7ed911d3f56d9bcba1ee3ad852bd185025422b0546b726d9cc68e6e45ad05bbba4d3e00d5195a80df7608b42680c9ccac83636f607f992533f4b9f349830db347cd297fce0c4b23acc2144657061904ce5166827430c4682c37f3207a8143aa696e4ac3e8144ec7d5942d3a53b69fb9d231b49b8f794233e8c32da97b160c33718b781bcca4689d49e3291da2bc82d5eba2963cb7cf17688ecf4c987801805b63e279789eafb3aebdf52269850bc9b01aad2de30f07ad5bdd984836fc7abb947466efc639828a1f4375a898ce57f338e1302bf6d25dd54680b81e60de9e675c7485bdf7cc379c23de21402c908b149d4e6058c996116faf5e2820d553f0af630bb8493616cfb983dab62264c34b3099c5ca407a62c908c6dd43eb76d12ef951792842bfa622d24f64eefe15fb31959d15a788721dc05c7cfd28cbd8f1c204eec3c41a8e63096b51c8c347f0761d538e1302329fdecdc2441b831c8a93618460d6b5389d2d630cb84918ef83172823f2bcadced0b2b240e8b65f5b1c98670294f021c03669a4945220146a622a348344ed89fd3dafde104701162812a8c357f1d9376ba67b6964fa6a46b61e45e096aa17bf01d08fec5ce48491580431091be1aa536e385028d4cdd012ad3451ccd93f8569232f82a89b1d35ff3e492c316273ba8d5ed96cd8ae83c02e2b4c6c94b96e7ce5c7725b8d8ae8e152cf50530530f0e4c1c10fbf85afc3a8e130c8788f33e99ecc3884866ce700f923964db41e470fcafa72f09713055235997fdd77eb6d19a8dee22ef2424e0e329b8f6f3a6f6d40d35033a3170d8b429b4e26cc3d78e099c065d025f37d023ffa23697f9b7ed886cec9828141bab4527ad70f38fcb3b468b6ab4c8d0edce5a4dba53f390733e9c2906943fd8235fe44aa5c5c8899f857a6d05f36f2b97b27cfe47ec8fcf249507cb40c59d2cde847d537f67db7f452f0cdb2e1f929cf816317eaf85f45ca4dcf5edfb4b09267d3d696144f7e90d1db43f4f5e56b7ffb6ebf5c7275852540d4c64870d1c7137bc5447b6287a1ddbb909e0ada98ffeacf55f6e88e199ac13a5eba92ded4b95d928d9a24903c0bef1e7fda9deb076324d4777ef3ffd0f121e6982d98f4a71dad83bb350ca18401f15c08a621b942481e03b92f999eeeec97f2313d722695de093393339210e9513436812679ad910d2640ba4c7d9f0f9970f6b2f6d6266dea1dff043c1306c60e9ceef9f0311ebfeabfffb467153a0efceb6febc1a1c09255d80c2fee8235df634f3951664e0c394f78ddc2b35eab08b8b420c84977294b0776e7bd39c71e152fcdd48478fa4cb31f918c50401db8bca79c356535964205c4e51046d23c8e30122ebf06ce7ab99dc6fa8d04531065e04e12e7f6e72d5b28272a9e52b3a87769c0b44d4c768b4d02885640fd2243dac89d37a63c4cbcd8874819dfbbf99ec5074128c902866ad06e8cfe6055923a18e97737f1a5fc6793be5e5efc324a099a25c784955ec32b1c5129726ada71702b56763deb0d1ec2ce14095fa16318e13e2e35345b1610275cfa1e2bf9f5bf08adb74c31e5bb318e492304fa4d02c8da5ae759e78300a59f04340289746cfa52f58b8456c5ec97178250eb1759507c0b6ae0faeeb874d27ea4a57a287edc6082424e57614e1816c4df1d7cc54a40d8a514ebcbd0b403a9fcf3f0fcc8b053276092caccdf109634a9ebfa2c37a4061ae364c8ed47e0a8cab6b3f2157c21ef46ef7cb3a99c57e3527762534337a4dc077fb7ad2a495e19ec40fa39ac167dede0f8186c6a1adabeac1e945fa7c2db814af654dc2b43055832c13a8667035578d88719fffed1fb374ca8cef7d7d7acf351dfc28a41156704168b9bec116a027ebeb00e651dcd6c8aa1840eb9f7df6b8277e33943ca8c5ed00ca1c2064edc01339248dccec4539aaa6ca535bb00d3e99d399121df1d15eefdec8d93778b04ce82d0aeb42d516fd59fbe27010ca3fd96c6629292680deb7202416fbd67bc3f91cfd8ceaafff5318f3526ec247461a2b804be66bdac01febeca0e76d9edd24191ab046ef602912674d35618a1069c808eefa6fba02a868d5a0b46abdeaf000843df733fd31f9da42141c2140e669186163882871efc45085bea4dc336a06a412c89145c44939e4543c6fd35841097bb15e46abd05d89c45c33fefded74abb866033da5ad0e6c32f518034509c22552630161f977a7e26f6567d9cf933e6c0b4993782fe92b4723fc0d5d89612e63a424f23994a35ac55f0375b187801d335567577ad5ac4d7b052829ffb4474d9e33f3afe0872803936668115e08eda17d196481ba9f46d9ada0bea82c8e49153990f421998513bf8254f18f931f7d46f62441429b262a731eac3e0464a0ed8471b90d01737ff494ce4dae2cfc59d96c4563142bcc14f9f792ad788b974b0d1bfa7930ce2e9fb87d832dbdbc6458751a74ad5e16f0fdce45886ba72d7f8844567290e64812821fa8d3e6a82c2375ba4c9bb0a5ad7c70325b93685add93bf669941bb74d70c0efd70e341ed50d7359e9ea41da8a701ff60a29702c307874531a0c220e565f7ae720d6377d063e081b9a7dcb80a5ee2baf18e55646ec264c10b4d817afa2d4000d118402c642ef89d283c666a536c6f7cd446b509b1fde31b0398cf7e95578824af780f0ad5ca5b7f920ac9cc59c7991db651519d66516b46b7812004529b8855ca65e5d28362acf5725a1255eb2f9f91d61fe7b310c695ceedd02d06464d20b4265723be553d79d6cdbd04d8a61eb60b30ce53b06af42495c816c5e4a13c6b32ad9e7236843eeefd65edfd1dcc2704fa87035c0ffc195259fdfb5340971b7c049945f4bd51d91e631510321f1b93f51a45412f9502a97554ec419a32ed7825ff771d198205744339a8844b87fe2d91a396cae0ddeea754e2ddb85579a2bad52641534fcdaa154b26f3b88262905ccaa06733a3a154b6ce06f2c7fe0f3ef2326711493ad5b647b587e30f1b6640e90a0c6dd4b1995d9cd80af94d6a68ed4e022d443d68417c3045fcfec7aa512708a790f3e4f5435e9fae92b9fba32bb8ae9339bdba4e52bc26180f649ec25b006d7b1ee6d6e1c7ec7e15ff73442771673bd9f706428869990e1f0e2c2b80b92f75ce9a0346fc0bd2e85bf248529ea4e88d334c07be7580980e3f7eabd7f5c3b7e184207c0ae831095dabcf7e6a075018f003fcfadbf88f061372c5368e6efa1c535ffb665ffa5d0ee59485631eeb1a61ef3d470ced31e305ffc53977cad5e75a370f92c8eb03011bede303f871c5fc801eeb7e76308b9ee444b4f3f47942a22ba5ceb55a81aff14f9ba259ca4cbe83f186f1999643b29e454f28a20fefd655f1b2d03444c8e30e6558556400e9252318dcc1b31d300d7bc9b8eed0f2800f7465442dab4243f543e103c4758289d6b9a889d00dc2416af208be683b044c4097dfa32c5ece9ef6b618c7842b63dc0a1c44efb36617edac203adaf9114490d899081bbfde5f3bb3f03cb81cb4b5d95755e0f91ab2e018657f0829c50a8f966070137e5b4dbb8c605277afe68f04010a9e3fc2fffeb0c594e9334798ad69f85deef933eb8080930279f9633915184fd16c7dba8296159eff621e7d91e170d2261784db16dbf307a6a1ef1a41be9d64b0e04ec203dbf4db222c0bceb4ab4945e21344ee824ff06f0145daccee3e0bb83989152e4aa93786e25b30c3ba889cfe16cc9ca2768f36817694c0344040f4f68dfe62b736c60a2deb5f42b4390c1d31cf071c8cc66e62a30f17cb08d16603e1a243dfbea514a16d4f4e688cdca2171bf71a02fa88b28ee420a9fc3e05841d5f1045e2db1bab77daff2173717fc5c4da310304bf02cabd873f6a0504944fca1ae4e72e63c8023571aa5ca0759a1108ebdeb0fc36c2bb0138953a1414e7694d9ea5486de780a08f2a13fe463a34a83ab687d3de4eda46fa1e995995061305d6f9c8d4481b6a748a697f077edf0c020a4bb94cd316b2135fa2012c7463f12e64417202877674ec6cae93324e37785ce8d6d73724510f1332a22441411c1baabda6148d9fd7d9a241e55c48727625347caf7e279ab8e6dfc1b0a8424a5592d58802833f0a9c40fc0924440ae9a857fd1e26c05e16168f7a355fc29a564cb671360cce5f370746b0127315c9cfd0b301180323e53741cee8750b1abc2a3d445e4d31bf6ed30b433821cd77e6e7ca5b43b1b9ef9419bd9ecad13e1edfc819f8a74d80f8a34db057bc0ceec7ff00dbc7836e31d690b78e4d1b26a39f56f9f723177f40fa795c3c3cf949a573b1c40c107ddd074a1c14198a70c9b5e28e24e2caf56de376bc69b3202ec9af24eae71417adb89bb0022945b06690fa8948a4f151a133522c1f112ac76f57165571368829b9b01a1988bcc6a6e883ca99c03299be26c12f30cdc59628b8e95f2cd0978b048fc2790df5269d77a4faaeeba74f64c4442a1494ed09d7d7540452e98509e9188dec833d463c3df5f3a192c082167aa73d0e924f9d1a5171a1cd46e718324ee9213c0a2f650a0ab25eb6513115a16136d9d177c5e3520b9f0a2e37a2f48d0c463a131e856bf2962de546046b648baa2b7bf0995584b0716fadf6c35b36c31ea3cf2e4db6cca31adcc95e83b3db61482fc440aa13c144f463d35b5f277cd007d9d70d2074d604623638826eabbfaeeb54b62a342395ba4eef65c619f6879def59154c401646f2ae4cdf0ed46069c4870e67e038ccf654ccac3f28f2a7ecc7c41353b3f261f940c33ce20fad610a81a2a049b4cae1a3bfca0118f4186249918263b9ecaff355ffbf833297145b35a96c71ee012fcc44e9095f39fedf29c9c0534e582c7e99ba011dbe4c83f8dbef0b6d79d38f5265620b187f9ece3c6321cb8867e698fbf39b993a764bf571e56226c4052c825173b0c8f35244c85f23bd517b9e034c47ced67b0778145aa0aeb680624514c029c673fad48a1089d0e3dac6c4533e12c02a1d169d52e8944c6d5337045c35b2f4301393a0ce3bcdc16d37f3ba4a8a445b1ab1496766c9391b4f960b3fc6b2ee79f42944c63598d6272e2d64f6283d199713dc9f8bbcf0c9abc34616d6bb8e3cb478f074d69dd4326f11c709385f6006fe62facd8a2a0d8012c80410dbbb8346f53e307737de928e5a93164f7855711d2e5bba0b76e6b279694816d94216911233faac0c492bc451d336fc4e315f0bdf4437701fb1eeb9e527734caa47cdc945f4fa6ef9863b27f3fd96cfbb466855a84cb37f29aecca415bfbabecea08e95b25473877bb3ff022d2411264e2d7e665348c2919d084dfc381d68001a7e42cd057d1a549528828986352220041c2000031255847bacaf0238a3816a74cf1aae2b89a160760af1fcff23c2db249046f580cd4140a29a7483b1621bb0dee840852b4d4db465b37d70c2acb12e56e507f38068b527a7a78a409350880b19a6743433356745587df9fc73b05e7de22ade02715dd58ff14ce35cf5614fcef141b0a5e2388017847370203649f509696d189e85b0525caac6fe7d2ac304ee7402fd7ea25471ff6045c74cfc6f1ca2d251bd096d5d5c85aafe5905017c45868332ad4b52ac5f181c57057e5ec22e0e5e2f6f1a6e59d64e1a76cdcd64f8a63d3e754e5f638b78f0f71c3c70705cc6513aedb0a2fbf96d34e2f134d9883d4e6d3eb271038403387e7ff852df5ef6e1cf0e766c2a8acac1da8780af5cb2efbe774a5b57ac094339d347631ccbcda4a9349fbd9813e237413f433457b3842a67b07cb87b0bff8dcb547116f1f1d03df4d0ae513f8ef24c74299399a3ff9935d59f8fd5fe48dff07c3426135c7d1365386298befd4599aca58c6bdd6cb4514b3a81552d2dc9260e4b8468619d186e5ffb121f2c03cef2a6edff91a7d16f1f9e69bef8158e284b6fbc847dfac18fd7d34872e1ededdaff3ebea8e12431b4749ab8f5bdeb08d36d794e9e9df9903ee06a590056beb70fc1d5feddabf3a2ed85094b325764943289e459d871dbe0e4a5d499cc24a8c82b78a122b5875ddccf1601f1ef1d47d605d74c35147e0e20f3d9151c0a64d75c327909a82b1d43353ce981eecd61605d3d8bea68386762f135e5700e9974a819d4a3e95cbc6c6196e34d25beab67111f8e73760b777c943241b3719285730bff856daca897632970c0e1cb2b8d84c74854da21d0f2bbb24c7ddd6e703634225f681c26a60399e5189709720ac9dcdcd86ee12820367ce553b92916e35e0723e15e54b7049bffb3f6e7b2e1b9157ab2ed750402adc80706b8c5b4f0b20cdfa0bad939df4ffd68ea9038c52ddbee1d979b7f7390988e59a6248e5d347a69216e2dcddcba930b0b7e9669acc0fef1759b7f1f5a512e7e419eb48fcebc749627ed6974e40ced179563d8c7527ebecf415a6cdc233ff54fa35cf1b60f2602337526db7e259119986f80505023dab2fe5d907dbed66528488fd15a1145855b9b153a72d5f515d89f8dc007b55bc8eaad64f996826abbe002aabdc0b4d4c5751c3e59dfa2406d2f48aeb7be8b6704e6a0450044ff816552918218e433d628a8e7700ba8f6020bd4f602e4f2567259b6008f4bb3d0abb7f053df43f3a895a87ba14e8e8af0eb405e00d7fb7b95362417bec6419974a74b121992ce8e6787e098cebee1cf082d45ba10f645f8161892e017c00b04e7d75254e415d690e3f3c54e7b418ed6c26ed91a7206e43c40dbb90f388450fae78de8983630ce13ca1ff4f353cc479c280ccc68220d12f27e414c8a515052180872264a8ade011c8da1115c841db51c71c626bfc79607e172064923c4fa03a1bc7183c3fecff88a31c155b62918a8746a6a3c3edca783bd82eaa00659e0fbc04f268e19bc1a6245c6bc19204236bff9175394581f290edc3937ad52c63684061a777dc48816ce327c4d9821f7d9443d33887396cada2b54dca3d2a817e5b9f9c01496412ea918a5f0d06f7aa62cf333a4185de8aec889b8868c101294d890c0772012f21554984eb000963bf979665d621f9d76f2cdd9c55a587e0e62de21433594a9f73ae2fb5dc867f8623bf6e6e69293a5ae96efa503bcfce2b59c71912b4837a80ef5754ebf06f21ae100c5cc6b812cb82d18acb4d7ca9313e67208e56e2131f1e987c576525c3370367f5f297b77c0591e53d72d90c7014a545c76a178101028ddffe30b5cfe06b27d0acf50aaf5c546d6258973ea1621002bc623a9533c62002020c4ec2eacd728ac72d4889ba9acb4cf4858bd7548991a45ef6790ceb035ae28cd95be68a883dcb1a99b6c5bd8bd30e2742c525f20f9728db0b9875fb28c242156f8d298c9f8610e302cac6f850f74c03eee7bc267b1f78edee18b1b1a94930409e4d99e78690274a1b256f43e334598c8bd46e3eaa32af6784978f7bea1121d32b5cad0b5aa4263981685d06dcacbedd4b1d8c10cc97fefba049c425826b7ffef68bbcf7e0bb79b06f4c1fee77fffef2ba4be72e1b7110a093971154a47bc1b2a343f2579830c52254954037715d77e06dcadf61c5fe119c0f11dcef5df1b08e0ff8ae621ff4b7d76ceb7bb8edb0d36c2d97fca62576c2237bdbd2df67ffe2d3581e56221d1945aa30526c54640d8c412d7cd7705e236193656fc6973f7edc1c9b95e6d316a13f7478b82cc6c4ca18815d36b5343714894d1e4dd569a02901bac26ed84cdc613a40dffc66fa41d60582d1c7150e1a064851cf3a5352cbaa8fa5f2363d906bcbfb8c4903001b7383cbaa7ddb31320ac0ddfa1610a194767c6606d868b8e523494aaed3eb0364446e3114f15e0bbc6af369dcc10a521f3d546108cabcdbad923f026991fbcc7b563b351e250ea69f081d858b5d128636d3c3c482449b13693dc734c4041b72b254bdab20aa37fca4d924119276a8dc05b3ded28e7a5d0ba1b2228fa74ba7ed69ba5b91017080bae131cc18c5247c963b1bbde378d92e046428e120e7c7a8802681f7661138265423b4ca79cc2fe30f788aa95f001629f430da00564ba13b312105478f8ca2a2390af9a3e01a187e401cbf176191ebba62bc91e5d7e35ee0df5d8eb5e91882118dc5afc57c47b853aa5000b759ab42e8b8ccaffcd528c7b19fb6ae81c21ebef29f5d74b1fa699fe2561252c6cf0d438e4b5e2b41905b076bf55724646dc6241614c28a5cecf72b9154c5d96c2f06d8647472f258f47a95b3619756d92815a9bdee326d40680eaa4d9841bddda1865a2e21ffe70e3a8b6ba02a2cf8c299956206138c622d0599f25f692e6fd808e8b32d92a8aa609ca917c950cbd92d907f2ae9daf2a506b9c0439758bee6cf8e4a3d154abae46765a06bf0edf5af7ae4f87c699b06fe3971fc7610b1dd0f01a6d8c3743bd591c8271d5c78509ebf54d2ee4b8fdebbdf2d7818c5c04a9d80faedec044d3e7e6795568d6507aaf867b6cc7d3eb0b88ab6aa3576c31b327d3fda0d43e9a7c533189d68ce19324ead593ab092c13ced126e51b844949f6ff5bc241cef7009d4b51e5d6f23703676afa3311d78eb022e056fbd9c6513f2b57f9e6e1643458727afd1235b4f48787216158e72fb1040c1286c8e2bcd16be2949c7b55d61f3d5923a5dd1257e9fd3f7d8f5938c29e6306bf9c1f25283807efa6cb6ee5ea3e48f905cce0972861c6c183370d83e22eba50bb4efa0cd8bdd641b6cc6ad53af9d86a86c7b3db0740bd43e1542d2ea746fabd6e00f4f0fad151f70b406fd7bf8efafd7183ac8d9a99bc0cd7a3418278e5d0c93dd0b2a6c9c04804250c1dd2bf22e3d04f082432bbf3a19dbecce734b5f373f9dd0f527341d1fd2d55612f525020e2a892bd684763b0355b70ad0e302bd83e486674899043dc1a9e604198e27e9ddb9db5f51450d9ac291a2f80ea78d93473bb981e060ff5bbcd5b90e6350f3de084b51e5b03548787093013e4c058e9b8bbb52b09dfbd22750e3e94ecad6539507142fa80926c8e0f5036818fe909d6c59840f484567a9610d4882fcad8f3ca8bfbe467dea4da541d5b120c157dba79fa20d4a81347add511284f19267977164846806d9b930139c86475afd3d8a54c8f47159dde2f118fc0363af7366c0f55d9c6e48ed8322c70b18be520a9065766bce838a9e607ae226c15618b7e93322382ba96967de951fd5d0cad38392a47e1690e8a39ab23fd9b692ef7f8120a4b388a7f9ed6a5989c5d1b014a6b4c209fd8d256ea9b215c38129a1560a9f8ca1b67f6c1fb932d52af7350148047e31383e861c6ee3569d53640ca0a4f0888854d776beb77324850c73dca032f173245cb0b12eb06891d440ade5352c1788968103670474c6217052fe48ebc8b5211470b06561fb81b2a58824e80e4dc5af76b4c40d924296fe46aaa521159de7dec36574a7d46aea43bf1a0eaeed0dc60aa6db471a688d274cafdf2604fda0accf069952533db8b3443daacd6a229e0977709978b03bee438aee3b0b49214b1621fd6184e4a8499946e80fe5c40ace419cf1f4e47692ac0571c1891b605dec8c8b78b1f65764f2fcce27568db1e1f523ed1822f40da80e5b715ff75f46d8d448ea48b1ce352f9e0b3017a147395bb71c399e5e0baa27c23d9765dad41123b4ed48ec6fabf0d7c1c41d3ff3d2e3ce6d6658dae45af454daaf69b204a75ed01d7211cd186ecb0511eaf3209ce25fe028c06c8f7c57f2c76176f9f7d2c1c06fae1b29377be1b8851bef3cfe3d861f81b06acd432a5d4eb6664179a138384c1d0f8b76050f6bf2207510c874706fb37e7d5d6c63c78170822762000acdfd79e4def13e01ea87cf02dbef1c6b786f06bb23beec7039f5b1d20b159b5eb17a6286fe615c99b2a59ad9ab4541c5139fc6c4d178d57cd850dcd3d18d63afef0f3235fde3b5720502b24fac311596c28d067073fc7f7cc4a6441aabe3c8be9e32615cce774cf8262dc0ad819ae593debe46422ba88be908d09c00ce2141c9ad556bfba88b151d0882e1947314dc028763168c04ad0452c5ea0c9496fde05b0c83ed13b693a0155d7ea09d02687302b6b4b2fbde85847673b4b57a4e0690f85c75c9b54dbcfd4010d1cf22c6ba3b482d43353b701854dde261b0a67103f7c165f55dffa450b1b2d7797c88812994184da33a2f9a4efe8b113a93e12d05a0cafebf25653d8a3f1cb805c5989b63d03f4858ee3878cc55a69fb52faeaaf805eadbe30195cbcbfa0929e3684332f4f0c27e8e5c9a3e69998da72a3aa21b227ee0b103590ff5a4a33eac61896716f08b5749480338170373f2736e140de1c103cb0e4e64df3734b6472fe1ad7fd5b447dbc985db45877ccbb528b11c98c64ee4d0c9d4b7f46954849c73e12242b10003bc7280c271846acc9ef102d173f1744a5c5f3fd447b14893465c4883bc3b481b2cb5d0f206da47117ef87cb696166c996c1d0b349c2b12012ebc300384188f6ec9c89811c7641292a4e31fbf7bcd383931821550f2588a26645d6fab100afe69418153690f6656aa10162448c9a0d17a951b7a2c8bc3b870e1142390c445d6125c73842373ae6169cf6fc2bdba7148c329d48e89fc4b0c404eab3202bcd9e5f8a93cdcb6536addd26737628d54b07881e99ab017dd4185cd08b49603ee8ec8b58cef8f374235cf4b3af034f7afbdfc50383d31d1dd3788707c947b36143d872399f170e846567821b2813df206298c637e30136e328cf1153e654cf6e15996e828d20ba9a99dacf7807bb21656bf5fbb36ceab804210873427b57fb6ae295b051a5d210a0964bd4240f0eda2e5c5f072eb32a059a14f3db29c68a9ea8f59d63c0845774663310a4e069d4bc2cf826c6460526832da917f2c0613f91e7586ab02697e84076665f0149fe01e03bc31f6981e3dc3bdfefc8191cbf9083d8606510b314654e8e2c04be371de2dc91f6d0a41f1fc35117ed2660d2ab4c217a0b612d5f1dc092abff119d8017e0570bec812284c393ee1b7479f3afa17084a4e4358cb25053ed9c8a0496d769e551d46dc1add76f8c17db5869bc19c326b8f6508be55b71f0424651c83e0fce1f626e2cd02d44118a830f25918919213a4b075a361a40dd85b8cbc2db8bd8786412f6b696fc28bbd725c78aee0f3010040d655c78188e897b7a713c22c9f54a83e1047c42dc7385d092b91d7735bed6dd9dd0063719dddeaf3d49ce77d46fdaf9e3551c1292f81df0b0926c666ced5111529c87a46fe788de62544d5c1c51655afd9affc2e7a03031d2c0d366ac412e4e198aea8ad06d7fd6838d7f7ea403816c7ab8d4184591a3247b7f2a5d9225238ec8c69122770cee269e92cab5520c094fcb77ebe47b232c501abc5be01405a701c9a2db9b0e902d13c9dce38b12423546bd49649fcf59fce3638b380f077b77f37ecf5da57c42bef3e5da1e30d6e22e325373beed43b9cf3ac25ddf583a4d92cb8756e387935758aa72b4dc6222146c4505b8cc36c4bfcdcee48a253b3b7784ae188a049cb09835c02015f13655859d6747048f7d508a6637206187037e2174d3333f0fed443267fd246a4f5db5e13a522b6e464760063b8235e7fb8e00678788064dd53c4126d3360b68ec74138e856b72a91330ee4387f96053e2c7f1d8b915cbe1e337931e9c0a0bdfccc91de10877081d06d733cb4bf84807aee8dcbbb04d3ad5ef823409de8d94ab8a0fed8e5615488299726270f8593e5ff06c04cb69e31d2088d0606e6095c1c45c06390b75cad1bc6569ebae18400966d58b4e66d0335c0bedf9388b5d67f0fc0ba6f0aa727e7bc5c4118bb944bca75990b3ff5a7b857ec4e0809b5bec39b7cb32f47229fd785f916c4ff6e92263eee8d0740d88d5652ab2c3856b12d71547a38ebda49279d3444e392018557356ac5d7f7fc3d8f1c7e081767c809dda03611b4a457b5b560e183f0544a14707709ee3f1497e4b77d596cfb584bd841026d049e970a57218697b095ba07419d1d6a7d4736a84e5465f23a14b4c9543048da15999606aaf4e32142b8d968be2bff3a083e0734ec3dc502036a6720b31c964f7b1a2300174984cc8491dd3ad85ee19c0da1c9b6e2c5712f73c602d0201262442c0b4f72293769170bcf1b7f2515a530dee7771dd2b7a100bc4b5b5f75f5659d8d543ceb52d8e201bd8779a3be437485ad47d4194725def1ec3b12608956c1311697bed58a587e8981a7354df28ee0d657f32e553522de33188f7ba1e896f8accc6b023dac1992231aba9cc27e029380c55c7e92b494614f3c7cd4b1a667d3d94930f16ad07883afe18087b50bbcfbcae46061f99a39a43596a69f5e51eed70c33fbf45dc5471273219edffc9c122b3e8eca0216bf74015ccef9c630263ca5e6f9f2db5423233caa8739e8357797ddd9d3c6da4c6cb71edb1f61e5daaac3cb23ab8a091e63522674aebaeae90c0680b6d6a28f9f5afe72ffef52ce99bebb777e0bf156debbcb0850df5ff7733f77b7745586bfd0a883813fd0a245698a0c38d02a2ed66ae4c8f24da018b863327662f64e31ef1707fc9df803758d3849aa79545469eb9ab82561abebe77fc4807a01995c525a2a249ae712b055d0896f02df00cb8b51218f71ed5caab3580af543cc859ca07acf72212294efc290699ae059071364c303133f1fd05902845d1e0f41dc177effab3d3999c914fe4e57f6ba815f60d5338d18702618f1caa7b049f6c9fc21a0edb2b120c61a89b4e00f689bb8975fafd0738d196b64cd50bdca6245e69ee12df69b553bf61837faf018d4802626f1b71b0ef21f99243d7a4a1028501dc96f5067ccd015174c025103e33799f55a863c525b905bb368fa05f589299c962fa9f85a95d99c2c1cd9a36e9a93e3a5677b39ac64d17aa7cdf8941a7266294fbc68809ff70359ebe46923d879063fcc9e6e21cedf0794edbbc8749dc99898886a4799d59256129a7fa025bac38ec1ecc046c2e720fcdeec833d4f27673f0c1857241d943a5b7492f4655e1643193255bb6f55012a73caebc3801a0601c637a7b792094ac4afc1b0438f9973f2e99a58f2e5f7977dc2e0a3b91f1b0528a57278c8c0b284dc330596ba731be88c385308ca6af79761b09a3e6187b202d8c4c356b5530c6896c58f4dea711dd3f4df60a492040ce6afba5f3e9ba752178c2f1e87ae596b528f9ba7e4fa275398ab9fde8684febd088375ffe045ba1fe52ff498307733cfa703e10a242f1b305c707b0920723dfe9d275841800feab0aa6205523623553c505ab80a1c4fb2d05b1ad9006c353e5f0881e1d08b83cb7b407ee1b725ff9f791744c7219490bd379c7f07de2b24d31d4e89492b1e53ed80bc741dbc8e093929bdf3ceb8437c2594b0b8c46a0ee255002eb111945c09a7f086c2a00a78250d88b948db7c54a404e000cb2cfc4cae76e7888b44a0a43731de966a9765b1131ea529e9e60899535dd6616ba173dfd9deef1429d659acf1ae4bd29e5498e3b58f3fe38dfc9b8c3ad9d9f41eebe438721ca38c2b3bf5e60eb26786b5d1e171a1058587bcc62c24d14cc8708c87554402e458d3ba99bcce740438aa0163c9d16bf3441502be95e238799c3daca312df6b77d318a42b94b874ef780a8b179786f55e6ad18d3ce33a0b4d2d9e549a56d13fc81246adb3c57ee995bf4d40c7d9a453ef6e5d463c10e2fe7c46e806a607a64b09c0420a59e90183d08efc53b7717ab9fe6a7cb21c8cacf19c70083767cdb0fa13229a7df599809832f18b159199a6f2f0804509bf60e52263d64b24b8ef7734bfca89cb025885d895e46b3b7660fb4e7c02ac67d59ce436f601be39a7f7cabdf5df4e72966bd03061df5004f8d49d4dbce945cbd46b2a43178dc2119035b53c1aa396b751030a5b8cdb65010b0937316be96b438ce9237dd4521fc6fe7f94b776af4ba8af167890dc5404bc6f6a03bfe383a2b8c72f257eb3eee43a8e3f5062bab990c74873fd0350675181ce0e92c069e32fb7a3334339d6175829a531a8e48051db4c41f4d8a905586d9cc90a3f4cf7b5cff1d67854c19ad8fa80450c70169745113f2f5807cbe31b24daf04fd250e13664541066317b6504735e4660a382e184e48a9c6d3f1e51013ed262b94eab1b8e0710b836f2aa03af24b007e107d09b25a5f970294578f4aea78cbfe5dfd7262996446be514b721f2bd992cce3e9d7ed9e6949261a873af1210a12300655f5148cb88c9a041615924b8cdc35d7e5dad4c5f2c8fd7a3919bbba0011140c57fbc069180daaa35bcc2d52fc74acfb87c19cde973268c8e58db1d50b466ba81eadfe6aad3716bc4a9b26eb7ab13bf63d9ad770a549467091123b574d80c1dda09e93b2494750cb2240e8d4e4b77530c087e59bdf622cbcd01ed82fca1f5037d3c2a4b3b97212462d0310fae2b0f818de010074b1f88d649e96f33c832ae9db38e27163189379750f4059d4987edda16d2a8e4fd23ad996a4206ff6b5cfa6ec06ae26b342f100729f69ce1a3d6eda2051ec811d57124e2f155f244e4713fa19564fa84b2f258bd499f9cdebceb9251ad4d12f320d85b6c5572a4120208a338196a548d5d30307df7e4188cb3a40e7deee562d2657755f3da7a47ce6c6de6ea3a340ffaf465feec667d17bb56a7a5efe6d439050e7a71ef4d2f83860450189653a3c143a318e6d9e5114d93a7efcf7f1d597db621fae6252905f4f4772bcd379c833c21d183ca4092c785d07526a67bf733157cf630894f523177078649473783eb48e947055cabfacc0bdb2793e24dea96e0a8078a620622ca260c88807796b127d76000126b9c235d7d0707dfb5c6f82a38b29ec7d3b30803257da08f6c4c043b68a5116443bde130e6630210872367c46ab9cf70484fad190e19112d2bd857eeb8bf676b38c81415c7391408ea0f8a6ede619b1182ecd420b37246f673846413a53983d89e042a253f1d171c53a6b4346284638e0541daa9c49139ce5c0f0661e256bebb0e5e1f3dc920b0bff59a1119a3ed5881de86b9e2308cc39abccd4167dd6e1dc7e2274b511d3072272276dca934f0be098077e3c3afca27209063b89191580c3808e0ca50cb5bc4a9d0d0abfb209bb584cb4e074e25270adcc4d0906005efdb79cb10ec33da9e5f8605e65ecdd268ecd756c50cc8f001b40164ed26178fb50f42c824bffbe6ff725f0a53a28aa094dc78ec86f83e23eba60aa5da0100488d0d319d728c57e1726b2b98cfa50d0c6dbfd44e4a338aa0d9657e3b2d789cffe738fe922501d63db803b1228628a71ed978a713fae21aacb60bdde82913be448d645a1cc957d57ab5becde6cae1a33a2601cfbb34811f9aab03c26b0cb9b3334069c1cb0a38b7b9100111e66eb06d5c0ff4a6e225a4a42157282dc1859a306b20954b05199fee561b20b437224883106022344821c6c3e1a4f219bcdaa55cc8e7c1edeb2dfe39a083146b402f8ae72dab7ee5d92e11f111a51ff53d60a8cf71034e6f32cbb925c6a41ffaa1c4d9a2cf7e2b55cc544905a89ff4de385c1d061f874596c4eb7f7b2ea7d1c635014d5254c5467c9c768c87bf6a9e804df9082e88443efc4165b56fa0ef5842763df9668a08be2158f2552612938cfcae63ea15c98f41bb3289bf6772526df7fcb3d7ae5806098592c37c48509acf76c6c473ddffb126c05841105bb9408bb499585a583a2e018440ef18210584f9d8d98606e194529005b56995d1ff7d39a3a2a1500052296e083e8875e65c21f5e25773d0a5a0f1868a16625b1a5a2c20dae8980e98aef7e9112264d35860c5d0911c98455ae84358911e74894aca55a492c6f44c6b31002095cb693dc8aa3e8d7ff135ad0828dbb76c51f1fa26a6079cc9e2cd49d238ed1f70574eb3bc2ff026da18e9def6aa38fe2b26ebba7fefeb1b72fbbf50cd6d1ede398362ed0be0abfe8add1ff6548e0f8f2a2bdf075e06b6a3249626fc5e48441fe2e5b40d35309188a0a5c8afece6c45eedf33160cd0ed773b570523ab6751fe4d8b3deeb30132baf243f5a7f2c57f5b50f374d5e7f6cf8940542b7d3ab848e0c211ebf95008279210c395d2f4d8853632babc11813df851c03282462e1fd0ba78023c993f2eb4341fedf99f048c7b9f344d0d4b7a07072c369ed28f1539d5f4c2a8fc87bbaf91079afc1ecc33589297355cb278b8cb940dee04b58762540f7c8f5ff48800d14b4595838538e92950b448b4f4f6c2e8b0ebe15c1b0d047b20c239c8d43288bc91da28e3252fc971c064a58dd3b8fe9cab0891afb9f1c4c8f59f3ea0168bd0d344285a1a86b35623105b031519cfe7ba81e747d4cbb410629e7548e7ff6be8d0d7f6baf779d82d3282284e1a7f646186feb8dd163e95033884bdfa8e7235d01a1dae5766d738e34829a8c33c3442a20ea80af922442355958e455c489d8a1caa286499c0d085500af93e2e7a1564fe7884405096a9dda51e1574901e67192911668d0e0e782c7e6847b7b7bc31db660addc123a7a8885acfe063fe603f83c4e72718245ec2427e4bcdb57b103a4a8144c2a7e803dcdc2d16a45ad42391ccf7ada234a848e9aa16a59f656d0c85f9e5e51869aa7cca71edf302f2de384e66c9a0d5188c807099fc9a2ba1cc927a4fe16115708edaea7918fa687dfb5c44e9ce4d25d1603ca0233ea19410c20c2cd4431cd3da9cf86b8892471413849a6353c317431a4fa311cef7ea89e5e1e9a33fc6eccaf2a458f281634128c0a9cc24c9bbfe46cedefead148bc889c08ceb1871870f18dcb8e8a40a021bbcc7c1f49d18c8d660a00a8e98e915e54e0eded10102c6ffa1bd4a2ac42fef3fa061794469759601d21fd4d61ca70cb2ec13292cb35569a770be35156622eb6b2bcd6d559ecf1036e135b14fe5b83a70d6c181f3a647212163747dd667de5c582d4efb88e5c54b25e60296cef48175a09ead70686aa490e9452748b794ba025c9956c9577faf1d7382fc17ca76b37109a2e6f69a0afa489d906027722b731a0a63e85a61d177fd209c2e70d3d7cd280b7874dd309cb0f2d601d871aed6b535dbfdaa40f47fa00886641dfbf28fdfd6c31ee9d8b91523174b2e96ecf5f84c2f315a0609f6147e07d25263bd0dc81f1dad95970d989b47eaf4444d51e260c35a1926c0d2ddbbd50d90c9b92de73b0c5b2421e83e65687d872a145a3bbaa5ffd1bd73146c50b43b0cadb18a368795850a3380bb6825d1288639bde5a19a341511187f1b5acd0de770946439efc17a1d807169b06a2194101a1ca5dd81ede9a9bdc0ef057006ab6cd17c933c4a654ba052402533d1c2c37388078204f3e16f8d7205a3ff4a0ec86cd5eb480af0951c24e6d033aaff973d85c83aa38ccff2627284862131f26b74dade773cad2be8c6af013bb591039468034a9b928e56204e69261d92994384cc2142a63cca627e2074988a56d37671ad59d75c659d2c816432a3c3ab98a5cecbb830bb13a4a4aeb44c57b34bcb26985277127108c5b272dc5bd98b6e3d4a4d7e938490bdf7965b4a29a5940147082a08420897083fa7a6cef2f0e3ad6d452ff116e0972840a6bb474597e816e1faa9a35f553f9950748f4840d51c50274fd110d67e55b5aa5b4b972893254a28952ed1bdc38dce1d559f32d020c577242c0af96d267795cbdc45a0f6da9566d2fc49162bed96ead4255a1ac1daa7ef8648874e11634b841f1f718e13ef78eb393dbc84edefdb1e856923b968475b08f5239970ff5eff0ec11de6f070fc25bf443ba7c651b8b7bf9528dcc43b66e07e5dbc91bbfa8025ba03a17624cbc3a3d6955cb27dad9bd20fe06bf2e4e709f4fe904441fdc384bbb6f5c1df5d2477f5c3a75d8f00924a5bf80e6b70fbf823bf6e1b717f47c2f6e38f250ae3db9e9abb0a501304f3a6997a242b896479f8f592ed2b5ed28d24356f62f808a592968e3fe32a239a50885f4a5f84f43cae437a9e2dc3ace09c3815b57b3cd43a47ebf5710fc2c2eb8b3d26397a4c64d822cbab9ffbe2f6afae36244d8228ba9529aaafab4745fb0caf1d8a4054ea804ef17efc95e763f2dcd86b53b2f18957e5c8faf7f97ef7cf9f3f645d7ffd2b51e0803f7ffe94d7eb6bc283abc743cbde93e32b87576f47a5bd1d44bab85f87964e65e8b49763ac8bb8a02c25fab5b6106e5d6806df5f2fcd4064c2e9d3dfbab4f668c812d55b7ac899e1a7875e7fe883241ac25e2e437d3cea807c70fcf426595e3f49a9080993e7e5eda797e84ff0fa57d5d4d4d4b4a3506cd16b5fd36c2d35c913bc4ed1e688553f3fc37343bc5a6b5dc1cf0d7ad5e40a72cfba5cc1edb93451bb7dab248a5594a9ef4b2f47d4972f8d087650ada12326461631205c5101460b55912c52314634a1b828d8bfe58e9af4d6e7d5e7f575955619441c57ae8b9552f9d886e46870faf4cb98973b124d35862232f2984eb83e660e7383c6653a01e5d1e114334ca76ce6c765133d3b335b5833977b080e3b344c4ce6b49897a45f7898996caa7bd410a9613a053531b1eda0eeb24928078e2da6938d8e99f13663e2dd4175b251d2e0347d2a3c319d1890c2cce590ede0665ba9a660c09e9a9898e4202dedcec0a1a66506332bb3e923a306cd74a257f69c9d7d31984e392cf39659c880b10263636accf4d2a30b9949c3ca569869c9222e53fc360d9363879569c90e2fa0d4a26f931dfc306f1305e3f24285e9f4d188626edb41e5fdd642c70996e9a4b788995f17392eb6984edf971ae65722d90eeaa33077b9f7ef7247e27bf0bfb2e9ce4e036647a26ca25b808411623639895dccfd4efacc93f93d5836994d4ea21973bfd3d706cab879fecd6d2274d64f3c77b01dd48fdc97c8625901cd8be974e574c0cb55d77bcbf16b4e95871e0d19ddfe5884467c6b5570cff46090f56658f5629cf5629ce9c538d3f445e4aff8331959af278eaa65d86d3aadd47db26b56ed925da24b02b6384a2a9779ec1259a8a87eedcbb046553f0dc96a50d6286adfa3cf46ade1a7aba6e223ab592b5abf57ed12993829fcac6b6a3b6f5bd7ac91b5465019edfee8c100ebc1a0d283b184480f068f1e8ca8de0bb5de8b18c4f45e805da507247d607917443fe9d773bce2836f5fff77e25ef1e32e9626a8b84ba6dc41f12bfedcc59283ba412d996c1771c941dde09e20bae6206710ba0451d6713f6ffea2ea01071944d75f4439a81e5c5aa633077503ac4b0e7e7c4d82e58969d7a2588e9f64f5485a1b08df8336f14752b4bc3d96f882e0a06e701fd3b08a57440a440a3d1029f4fdc5e170385c2de39b04fde1fcd5329c16a171252a592da3a2c59167edb812b8bf9cd69ad570da45b367b54425ab65b89bfe1357eaf2ac6520d40d92b4ec08885a225097a766ba1dfcdbd317222925a29a96e589b15bdbd3db6f6efb5b7edc6d69c286f3e37e97f7fbba062c4de01eff0d579ac8b7f196cbbae6569eb5acaeb1dd6e9f64c5bd7321605effbeef44ff4ce81fd5ff91e78b90ebe7898e825cff4a131984cc4fdd6b507d0a00bdd0dfcaeffb3e9367eefb2341215421529341e46889f27f24f837b286007e067a4ddefe23f3df8248282ddb8ddc230fc90e5fd7e07bb340539efa77da83a2a2d6e8b535fb4a8259c77d0a7fdff7e9bf3611559b951f9975f3fd9d3af7fdaf3c99ce8ae2fbb15e804e7b2eb67a1454e7d069cf855917ef98d7b0da4db368b5ebd29e915923fcfa6d59f580765d5a33cbc3e19fd66cff19961a7f3eb7d09fbad70f44027df841240ffaf08548fd4124101932651cc99471c994fbedf77f6f6d21e0d2c42de7febd75533d084b13e3631172c7db9a75fdd44ddd406b3c521b0962b7646eeed1de3c2c753aceabf625baa2ed6f7fe9f894864f69deba3edd2dbeadb4fdad3c754fe5dd6558eebe9f7e25bfaf4f9bebea934e776e1b08fabf5d64e6c6cc8d991b3337666e72509bb13eb6e8eabc4e7b2da4fa7ecd47db96c0feb03c45d81f7ef8d546c2fef04b84ff8995c9f6efc5b29e1f9bf81eff0d33bfdf8b0fe6528322e2dfcf6118dac84a3f843f92967e085f93987e08cb93e95a5b08fb4d88248c7d3f0fdf7e5882bb3c5380a27f6559d7d40d802afed36bf1d5c51eb240eb459df658ac607374dc7b5032ba88a3e2b64662894a76efbdf7dedfc0a57f3ff75c668db0cd218af42d796dc5186334fa5d9c64e927d97b31be18638c2fc618638c31a61897e7098c31c618e3ffc4ef52da7351d53be8b4d7c2ac43afe4d9825e37903a924306e48e7eb02f44762001406a400640ac18a3b0cb1387807bde71bd5fd350a0399df54a7fa360d671c79128f86a79d635d583fbf44bb047baa75f69a57fd6b34ebf524a29a5f42f2d7365f839e29cbb88c77d6d130b92ee3df133d94e7125e8dfb39aa1e0f6b3765c69a2be8d845d9eb5ac9f4ce2e3176d1ffffc98f091eebfff4e8cf5ef7ff4717f7efdfb53f7ef4f1a42e8bf7da40b0afaf3f6dc7f5f69080dff7d5fbffa95de32c95ef1cb12146370c418c46369427ff838043138fe3d31f6f04fdbdf52832201590c41159f95b2515fde5fcea1388ae17747f0335bdb677b4d345bdb6a687b6dfcf6bee1c60dde6e3771bce172c6371ccedfad4a0c472efb3a3bbaacffd3daa66dfa2ff8177cb18ef9b37581d9bebc6c602078b389e158c5456443388a55df57b9c0328be0c6816288b3038afa9311661154a1bf6f7f790059221d0aedc558ef7b74cb4b4a5f205814d43ff8432d5a2b5b6593d829bc439bf5e92424a41d4e6581541bc21d623b65b3406b65ab409ba541361bf0003dbabd7d16dca35b7982be39533b3163fff8d9469ed4937dda6173d40dec13ed9a7108da270d85310e41dbb64f56ca46d91c379ccd661311be4dbca9a1ad9985a05eab1b54519bad7d67dfda867ecdd63e35b46fadec57dbccd63e35b46fed86133ff076bbe1c8c6dc8495dd221e2fac4cac4c0c2b231b2b1b6d6165646365371cc6375b08b6db90ec51edba44f6f7d23dba5d7c1ec07211f168085b55088a5cf68fe82e11d65d196ca492b7868db5ed7665b07104abfbebcefbeb832128567111d980c5b08a6bcb8a8b8fdc26623f1fa23548bfed107cfaf946660a82a42dca1a01519e96b1414307c88e4b20416079f6c70d9ca1d275aa4ae94a05b43f10ace1d600a6d470fabbd39e5696d33eed356badbfaf24ed7a77da31d665ba83eadc1b5b87f126c37863282c3e61a81b62b108da5931b2c66845c952d3c1c7fbfbb270f42f4bffbee73a0ef051ce198b47cf0ce8194bace79c1fffc5636304f0413f3fe837cce65642fa7d9d955abfff48c2fa7dde95917ebfe85ef5fa4d3337b8c3ab56bc23e31b47b6ca8e7f6f20b51ed1d171c7ff6d19773ceb67cec8133782a4e3d73a1d1863dc8148908e3111b08e9f17d121028f2ff8a9d3615daec39087daa29f203ee858cb49c5284ea36b00c01853754c04867ed6ac8e2b57c7f8d570ddf38adc74fc48494a4b55773afe12250ce9f84d388156c7cf8214a43ca9d01575d671af8a4ec75fa7aa5410632504c91654c75fbbac11ed0d71a1e3c75e6131a430af967f83a4d02d967f17ba666c207a745f34be11eb5f22fbb98bf6de31f15b9a2da680d2b5d65a6aa9a5d6be0d42e3f4a96d092a3fb645a4d2d2252aaa8f77547b6fbd63dab7f66dc698d4e132c80e08a4226c43ccf70e59a2fc9697df921e2c514ecad4ab7e25929496ee91fe4c56abe6dd22a44b9475417378dd4ad6e8c460297ffea41172c7f6f3634043e0cfbf8186b83706f102284cfc7488b4216e9da8d704f45ad205f4fa4167fcfcf4d747025fdcba77280ce983a4072c5ddab5c5552b15bc936cc8c5e993b869e647e9eed235643fe4a108e903b35e817a0532d6ebb5b45639764ac4d2ad50286c16a0262eba44481bebc4548de2dafd24d2218c731d2c510e0f3f8bfa6ec41df3fbefebfeefefeefbc520344a57ffd5ba27793b288dd78b6477ddba4ee8907e14b9d31c0e5e5f0caaa2321b81afcd5db27ed23429213f844851b21f5338f47ca8651c14e4fb5125a69f342d8380833af4a7cf8c98adb5d65e4a29a595d29cb556847c420af09cfc7ddf0b7cecb4e763ca875459febe2fad4c3fa997980b76052e76daeb9125a7d0438a9a1e5a5e3c77daeb6125428f2a3c0583a59b6fe8b8d3de1499b01c433cebb665eda50dbb78c99d08f4eb97f68963f20dcfb6de7b6d0a51c88ad7df4d38bdf8da14997e4544b15a4bf64e76f1525b2f2e71c518c594b541cd6c228c28ddb46e401f4f155d228b97703e7817ac609847dd5863dcd5cf37eac61a23c608e019a01ee9b7e5c8c1a81b6b8c1a94e161d4f044c313dcc5dce9a78f08d5e1afd3fd77ab07d583dce9570f481e6e78a81e501153faf56541c05da98f4dc4edf4478cf1bdd503a50a817a943f971e2c9135aa7fd230d50392871b5a832c444ee9d3be7596a7734bb92f9e1f8345f899db9b73fd9b64ca9dde7a4bfa4be84fd2afa4754ed7617fff81f0bd987b58f67b9fc91ae9d7ff81e05185d2daf696c0d0b5d65f0208beba7e13ac9150d77fc21ad1de92adaeff042c00f797d35a02e3f7059227d38b5cdbd725d75a6bab43510cb5d690a283e5616bb14e1ca1f0fc670e8b6189ef6772179621ecbf4ffb0de17bf06907b74d89da0b8640fbc9f49adcd5dfdda71c0fd1f6cf9cbf9eb33af1da72472234b5de5debcf5a6badb9e5695bd60fcbddd27ef0a9295afbe1dbcff5e159ebffb4ae52b786b8eb0e42c34fce7534c4d7f1ffee185b5be29c1ab76fa3c0f7f6f7f7160576d1abb565d3cecc4b7cdc5c2a4bdc91d028ec1d899b66ee17ab2dd307642410995a9b88fc0f400108b557809a942e695a77f67724f6d6e42ebf7e6aeef212d524bd97ee6cbf23b11b6177d9da3edb486de25af1f7999984f8b6b79528d87e47e2a699b6b7fd0911a85dc4032cef6a3151e347ba447787bfe812913b5c2ef14b749fa7e908a4a2bbcb6505a8b94b6ef23650422c03f677f779f7f97dddfdccc7362fa9e15048f2ae784bdc23fa489fbf28d3b0f1c30fc3203cdc7e11efeb58ee4694b93e9c6a9c9bbe179f9a2059bb8bfe7590b4dd8e1908c39d4b0a3be82c2fe796ee8bdc45e8a2536b64a3c04eb78b7dfb7a6e8ddfafb54a4711e990cefb12e5a31bb923714d318787e77ccb39a7c6ef5b0a8c7fff96bb11e3df31c9eabc23f548624b94ffe6f46c5b066eb9c37f7f37e2fef82559f104621d3549997017fffbdd4bfae59c73f540bbe81918bfe9f663b9fb6e4f6d37ffee2b73cef973ce6099cca4ce625c0bfc1e55a0db27d9a24e916808daefe5f7fe25c20f54f24b248797181f771fffd23db23d7f8c8b82e870b09f455119b8fd2d9beedf6e08b4671b17d78fbbfefbb47ab03b7d14eedf6e26bed72804fd75ea1fe6b034c184737e1d8adf275e26bc9f82e5c9843bed597f7c29e9ee480491bb5a8eb80ff4f7819e0291f93718fefe7ee72d1cbf122791025e59859326fa881324544e7c602d4e5ea470f2c4e18d0a8c87e72e456e745d2e1c77dabbc185a6d5bb610493ddf8c1e14dd6ce75181af5bdf7b3dade3c74eb43196bfc61fce98cafbd9a526dabc557c9f21e6079f475babd0db039e8f44374faa54eb7f7ad9452101f68b11e34bef66e3073626b759788665bab4d04ea65edaeb52ca238638b91881ae37ee0d192d6625e11d283401ceafe4070c83e1952cb1b474a569bb546401668b43c68b45b0ceed35aebb02c42fa74d59fde5aef2fd78bb506cb73eff2e47da8d36b8431bea34dc327c6f80e95e95c6479f4e9bdf7ea718dbdd6a85eb0eefcf5db64636157e912d55a6bbd0208c5d1463616a6346eb48d9454549431fe7abf02056d0d196390006198f3067323cd607ed18601ff30c65ff3fd0a5e8cbfe6fb15fc3e8c33b7812f068d985c812ea70167ac61c3a801e7bc61c3dea0860d63ceb65fdcc0a1921c401130878a3f83cdc1260253dd7318790f7ead376b0e92f839e7b8ac15a318410968c61787ad5c10b1ee3b668c33a8252e11089656500034c4b527afc42876b0e996923ee48cc336f0451d045e1130581220dc1f6add81bd69eeb651b4e9176d23f8620ec7c1926e909f49378092f2f2132003404024fd90bfec59739b88fa25e8d8581db94d04e739219b88b11025348c43d688d27a9ff7fd5f225bc312042f3d7fd710dc1f59c2dd802e41437c5ff502baeab347cbb3f7c538fddca61fec2db9197e6d938d6fa723ef4b15162968885db9a23e9b08bb75f779b5e209a5714c4bdd444ab72ed87bef0563a6df19b88955b7b68c4ffff087d7af7e15d0ef5d7383c6921d5f6ff6d830af59599e3f35cab28a68d919616ac091c7ab4c88d01aa28974e8bc1bf7db7e9ce3b2a986f5e88d994d4e7a0616f37e9313501e13bf53d532626a78a1dd880d1bca26ca05cc6c72aa55e6fda616d6c4ef64a34c0da5164873a41049c9460b641b81ec874160474bc93e4a063472ceb5aeee46dc26bb5536553466a6e0984d4e59862266ee9b9c707d96cc3085c26c319b9cea97992bad97ed407f935336ef3bd9164ca1520b7c14a68621fcac5f7c2b2c4c1cededb21de8af60f648e9d62f6b0434fe99eb67fdb26358174b0d54da30a57b64c52c917e9d3bad86221cd7bf645305a321e38fd9e49481a09942ef645fd0693a8222546a6910c29dec16f3965ae068bfd17ed436be25475203ac12bedfcfdf86dd231e3771f2b7d930fa415b305a8d2da3ad977efb65b76cd73d6aaa55fca4e9994d4e79071bf3be5335c3656204b0309b9caa94794badfa4e16cac4a516d89bf05a81585b7ab06161faf57f251fc5508ba66bdba6ebd3cad97a5b2996bc0cb361568c86d05dbf1db346fcf5db33d6c8a6bfd086d9303b26023d5069c29664b92c4f83a4ed0ac16eddb8f0effb057ec3f08f27003dd3ee2730f15ec9e858b7619748077dda86599e7e20d28ad10ffa3969c72c4f3fee4696596b66ed9945134337d0bf49325dffcd8bd6da6ed9adaaa9a6374c2726ea320dc19fe65c6479f9ed528911afeb130812c9eecdf91d9f6a8b9aa16808163404d655e14f6a924aa3d61d35eb0d38ed4159ebf72f78ef2d3b800ff43991cc59beff2d2ff7953952880c222530dafe92ffb71771e48dec0085e3fed4e1f08b3a3f176103e29ca41dbc8140385cd9c146d2fe7de1dfb376f12399727e6e79190f8d9f1f881e155dcae5247bce9f291314fd90bfe81e551694287fa6e2a2a3e7cf336e5e78130b2c2f3f0d4bebf99772c6a510f368668235fa9eb2e060c75fd12e51be5fcd2e51d6755d071a627ffe137217ad98e56536fd84dccf6ac6a29a59de19fd90ff0686db33a51db3bc6cc5fa7f66962867a0db73dccaa211e9d06971fe4cb847bcac5396a97a90cb6a836ea05feb8a837ed07fd2192cae523444a62168d7fa2b55f5d2fb4b2f69102c6b14d502d564791a083fabd4dfc6f041608dcefcbbad4718618faaa9cb1df8b4046b645feb1753f0fb6795c295b7b22cc312aa4aed42f13907b5ae55f7887ea12204061a2545acd698faabd53d42b1a44e11b0373692781162eaaf52750a4bd10ffab70e7e0e49e1df5586e5e9df658deac13525d2bff7d01db22bf8fd73a86bfd746f6a835f2b259ec9f7c3bf35e1f7a9d640be3ead659697a7c0b29af5e025dcb2d4f57bd36f135fd0231128517e5d1f8e87773063f931a9443fe4ffc85c643bc8fff1a268889bae97bc4b94f3c9eb190f59a3efb3d65aeb124c28c2f107a1e121f8126888dcf38360e3e9b94f47cbf60352b49b706a2d905d3d94bc1e9ce65a54f558fba37d5c3d7cf1fad4e6c1729d7dbaad589f2d2b5cbf90bdbcb8e17bcb8d6125e3e0e093395c6f59347c5badc07755e6e2208fbc876b1bd806d79df6b66260395b5076f85eb332f00f8f25c383d8f4ca6826605250c2658a151b87ef4e7b4aa0c050b2a5c42ac99f0f2c7fdf87812499a19fd48b8a0bb1c13ff84d0c0c7cec494922e58671b1d35e12255538069df6924ca14912e3e6e0419df6b058e82b2d35385c43a73d2d365a4b4b4cd81418df75dabbf1d44fea15a367834e1dce3bedd94823c6719df66c84f9b0a1650b17eab46763091f1b433219cf75dab351638424252d310c3aeddd080b13b735e25643ce0a055d20cb2dceda9b6d146d08dafdbd526975c6b7a85f5bed57bab74ec7fbb576c38d084a982b79f709b833a1e8f451745a666d41f038bef7de925779b5568f11a9beb75e8cadee5783fadeefdb22dee40e9720d44e9f9a5f1985648b2e0fbff93db83f727f9d466df37b718fd7ee0a82d92602df7ef1a694d211b4699be665db486ff77697b4d6f7761096a779f8866d226ea4241c70de7bf9a8ef0622b9ee4bcb71ebab1fe72b8487d7306a3f1c8f7bf76e2114370e67e7e67befd519cc5f06c52f836218e230c4a098c3b0fcc28cc3ddcbd74470e34031a4f92f2886f6e9830a6172bc38518b2343d88eabab3354c0f0d8a1c20bea0b89255570883c11130665e6069f331692b870c6ca17113663ec589931858c8d5b8a377160f852464f902a58648662380227868a175f6e9c6024871e2d6468cc60ad70c521948ae14c18588f179e70004f0a602f2451f3220b15240c88c18e171b15f0e428f2b59006e54346575498294836a470258e131b5aa24c617d10e505581216f450d191a25be809a1f28387581a075bdc68c111a3ea8a5614211d428ed4f1f3c20517a64ec1ce1a19286aac7c745da96122c3191936c4e69cd13c635a78128354932178a0e87254ada9816df1f142050d94332b52c4b162c4cc13ac9c96b2277f56b0e15263828209d9923f549ab4a922c70916065074fa38b1d367cc183c402af86963c68e15590a2e0fc8ccb133054691192e8e0031e323850c3e4fa47634f1b27a11e5c81b2b2b7054c420640a11183feec461a202ab4e921c78d87849d903e58f1a94971c49a2dcc848b3244b01a5975698167aae7e3c8d605c2c2cf942a5c665cbe8c33021a3e2426a4d18314eaeaca4b85a21ca1325571cc0b0e7ce911c6651fc08206d052f44cee889329504c91e2a685c7cc1a3664d5a8d31592ebc5ca1c382972bac959580175d547831660b4794a9531c11aba3c7cb1358d695981198cd00e227063f294c49b103c58b0bcd9a932058383961a48aac5027cc932c3388e0d953850b4e98922bac2b26d041252549d6da10341b4a76c07921c559153648b0c2a0d882ba33c546131e30bac6f8f1d2fa532609176c85e521894b6f8e151695143950acc085cb0ab4ac3a4668e02368275049a5d91ab344ecc9ec89941753a8a8c932a2411b2c216dda94a153a7cd8a145f70908dc12a72b55393c30c2d8d4e8d2a5e1bbefc803323eb8c0e1644ae86967444e14344053e552d98c07cd8a9f3630f1ada10240fc4e5eb4b8a307f5a58a27b7267863c5e75faa8c142908c6e72489901ca9f2e7076dc28a32a8ba306cb1715aed86ce1e23583d91a255a4c2db5438b0a1c75ce66545dbd601567889c3e3e54b0d2e32085cfd6d48e3a5b62a48055539859c3f25484052436c4ac51db32031c3029478264a5618090203eeca065894af3c64eef0fda982e312140b010f082192cc8b8b0aed27a68ed594105648dd61f2a2400188136c7cc98b01a37b070e4393b5215c48d8c173faeb209b4e8697105cb9e1663567a71be44b1b346857dd019c06c5d41e365ac07950a40a096ccc922c393203db2c08aa4a674f819436384cae9499eb2144f79b6aea6fcac49a1cd10192f600cc162260675658a0b0c952c3cb20280474c8e283b4b6c6485e1c0dad61633542a804ceda073001e2da63041a3244f1c0d7ce9d365b68646da1f1d2e58e06c869e273fda88f1b2801838a4ce84ad0dc1caa1418f058dac3264926c7540242bc911364ad0f47020591381cb4c968ea834516a4ad0d5236cec0d102b436ec8c0030385060e3f63685913f0a203466a4896322c6c3820863359666fa86a80a9616aa935506a2cc12ac347ea8a05165e148933c7cb183233c870517db549e1821a548dac1dd6258e1f2f40749489c1d451b658c961f2b5468dc655d9038b8bda952fac3657a464f821b3e329cd97196e30a096038796a914738ab82983958506cb131c2a646009c0ca940e19525b60634a48f0a2d50b03054695a818575b10f084060e2579c67a69505cf11893a68a183774b4c0e073678a4714b0a719c458c06111e931a74cd91bb217714a5831a34b1f312b3c6428972675f4d0089235664614965a910b21643ec26889c18562488f1a6dcabcb051e107bbc032a3b585c9121868c090a1052c72e0cc60e60532dce28a8c470f3076f65c85091392024c970c3d39584f5c5864958923e3859637625e28dbf1638b99153b675758e080ca22d30a80c20e313573f8ace0024d979f314ba46c4122c667c5ed8f9438688e90b122232549c88e2f2a74b0008312ac62cd182c32623d346fdac890000d3471b260a03303951458690588f2e5ced7d8902367c2a73c6ebed2d4b061033f5d75b92acec8d046ac0e942037b46ca18103a5e4ea0f560800d8b9d3829438575f4c7dd64556e78a912630f8b1cac0459317a460a5a9f83262ea267d604033039a1b1a7dcea6e88859d385cd96395a2ac005c3959c3160a6b2acc1219221a7851573d86ca1f27d7c7ce5a0a26143079f2b3c344c941ad91d225a6b74ac008cc69f2a50ae74ed41b3848b8f33292ce98241e705646333a42103e6e2851e2cea4505b12b3c45e8f8293335113f606cb820862b8fda95591b15d4a0ae50597132c6022dba28c9e20226a72c0f9606fd7b86a429b022609ef450f5d0bac2440c14b338447ce0619186f5079c2176a63775c6021a019620b9b291158385a73b571f0858982205cb4b902063327801091d2a6a5ec6aeec20e9a0e12b4911372eb050d1634d0b2267a47a4cce94d4c127765cadf0a3c7580e245bd468edf191650d1c2bb40f48c0e3ced9191a58e040b1094bf383ca0e1d6c9cd0590001c86c79f241050a0b1f5856c4c80ad247cd45d49c371dcc0c910105b622491153f7bcd4b17ad1a7aa4b0edfed99112587880a6462f0c10215b5828f9f31206757588a727c19f192c74b0c5afa1e18a65e4812a7cc551c0a830c2b70ae98b8c91283005d3cf284f171e1434d06ac183e80c74f1e34b0a8285f2b6081a38dd9182339bee05cf5c8155b59ccc26ac8598182870b5a29bad0a849e188195688812b1665529ca59821a5a6868e3c565c86501579f301858f305dc6e8a0e9313162ea334ee2b4a94245cc15183758f48e06606b696278da93c6ce01fe7059990153250b4f1bad475a9f34065fb8d6bb0e0f1680c46891624c0800809ce9a9e1a7859836b7ac532f8d41d888f5b5abd716eab44745eb0ec954e35b75edd25b61aceb1ff2509e2790346419227937ecb44765483f876c976c8fead3081506a7bd15be3a50a73d2a61bdea5a10b9a4ea18ff0ff13eb87f82e8203ee851e931846479f53568e1b49f20ba2541d80d9df6a8ace85fa7bd296bfde4f50a6328530370aa010bc7e2b5f6de13482553b55d6c4ad45e71b4295c99865db39ab656d5725af859d4eb99e5a1595ead516647e1d7fa4b23b5ddbebe09d7ac8817d28d6f694f8cff44b27de9747b2b255d22912c0a419d7fdb2230ba69d2a3e71d95da6523bb5c51c2aad159d36a9a351a2daf8617a887071a82f635bd3ecfe986acd1f7fa352a8a1245d5287b34861d417d5551fef42a7b0435864989d26b2f0a59af01e8b53725ad7ae15b7926d4a81ffcbc6a7a7d134e9ca0c91a41b1c01a515121eb224b1332250a0a2434bca099794d7123835a6e06ee5a370677ce7bca903d3ec0ace08ca5ccb1dadff7c5c9b3844819940c3680c60c64e62e37488ad04f3f7c2664f03e0a53dce4091df828ea06491dad24fe0bdedaef53b353d76badb608cfafcb13ba7edff230b9fb26e9db9adc7beb1f2194a4a4d5df67a7d0fb94d224bb85b36f69924563817dba55cdd65ad5e8efdd4558fb842f9a4d1325ea095ac727ece8d7be1301d7e9e3fa1117d867c2fdc8d37d7e8968692d6e53ea646d2d63bd0440030074d865c04187c1060d39a120208ebbd9463104f7a7f327078d786f01083080210194e40740030074d865c04187c1060d39a120208ebbd9463104f7a733be16630c7ad1c0a2f7a40ccd5553d65639bcbab95fb87ea108bf6d69b102b6e222139cc2d58f27457299274038bcc952b29e48ad71a13e188b0bc9908f706ee6fae13b0c1b27019e36a8e4ec174539e90cc3060000000073150020200c0e87c402a14090078a2ce91e14800b789e486a4c17ca634914c428088218863184010000008010028c51d2d819000ac57b074ea7716578f8d693acb73b232b83ae763746435fff72019330dfabfeb8bb79805b382a61d094e1de4f5ad634d7832e8e0b57ac434f5a0af7502b5c8bd86ae74c51d640bb4312f245efa3eb13f3e4921b40e44bebe1cb72adfa155c6bd63b26553bf36b74dbf2c6bc0342da42f7b311ebcad042b7ba331271d372e14d726b8b9b4289827b93cc5926b98a06cfd2e1018c8592abd726530b512a9a0226f3aeddbbe5cabc4caaf64d9de5ff2c2b8b9b46054669f1c9482d6f9eec4ea4e65a6fdc49a06a7910ca4bd25d16d5e5af6b61de1f547e654ce3ae586a4aec6568e6e1f03ab4ea52dc872ff83f11612792f88a3a1cef60705a2fe356bb8b2a1e5cb9f07dee1c0a3aca8c3d7a97bdd9664dd791cd4d7461123d27ca07417c692cf285b8e55510e5960a3b50ac869ab4085775611489ad6e4e125d79f0b35c24d9b6262dad02af04546065a4ec5e2bf6737b46b427aafc31142d780fddb7407ffda4f202bfa722d51f1bc0e62679747407095730f7eb9b33d52cce6da805fa75f89d68cb6472e93b48cf955b99d67770d3cb411f72817fe7c37e6eea0215d86249cae6748decf4c4be5949eab8bb4d7829bf3255ee43fec5b19264f8f6a330be507556afaeccb4ba6315a65b629dbcbf692f1d0b9b50f333a8e5458601b22c975b19559da821d06d93b6b5c2ebe1cb8bc3ac98dc22624f9711751224f225dc2e0a13ff3d66f3743b26955af6f716e68b0cebfd1c890b44af9478037f526a68675926137eaf76293e45961365720cf307670bb7b73dd6e945ada7105f1c5c7141a9dc94bc59b2c6711554e9c7258fbda4e4dd17bbf8471ea015ae523c4a10b85de75fd0d474837283ef4aa373efdeb8eaa06481375812e3fcb98143391cd15bc451c4fdb88493b640f44a960650c8ef9ef1170ee48915f245f55761f4232d4e925e6f0eeaa56559936a7e075f9ccb5f73bd55351cd7862f7877558bb25e3909a3ed6c51e865d17f61bd7e32c1e02f9acc8bb3ff79f0cddf24af2f9ad8fe128ba4d0835c260ab8ba652aee18574f16fe244cd353954c89b7a90bd409bbebdd11c364a654d34b75abc8bcc4aa6a717d6a1f4061834eba8aff2c8b2c9133b1c917774af612748c6b9d34963dea4a594db16c2a3510d68b3995c2a29d90b125dd2cc935a5d87501a3d8d009f720e7de44c8e8839eb82d3329e4f820c1b9bf718bd4eae957cc417402260fc3db055df09fdd7839d0215f0e5d900bfa27293bb4ea0acca2d0fa48dc83c3ae1e9827d25a02d8381594f1837e694b5a60ad12b31e5aac20c50e357937f345390d631911924ba68f3326e047cb09e7b6369175f1a5d120501a0c61e2ceead908090e89fa316894130eebc725b1f3fcc22a01d28af4fe32651f33e956a64cd671e1088376ef6aabcb4060d8025603ba6b5df22326ba730e2c31d4114a8cd80b09344db27a06914f605b585d02d5a12ed98fbc17b8ebad51f386981cc5b55ca1e864eab32220eae95869fe35a2eafede50094a17f226323837ea0abc60864ebe6b04b6a1173d01666e5700669c66dada68aef400d0bd539c8ebcdfdbadb990491332e4034c86683cec40a8d74f1f0f9649a5025f9d95a2d3fa741c57b1cc4d9e54c3545a58f4eedee6eb168879624e06251d9936b4b0316b54eceb3ecd1e9719d879a053b3c39a4abda11303e1f361084cae07b6a8f64ebd5a82cbff64c9f2df917d97e5889e18797dc6b0e93a8aabdb3ec320bc3a5f0e0153266e0d38b4e47e2130cf44b1423e127a58c43ec22812ee11a01dcc066a1727410ca43a8a896050eca952c77c28d391ed0a84ccef397d3cb83ef5dea2312040f3468be036dc0521040d0f2328264a3904ecb82aa4432abe2827f2fd5ea20a2b1be72721ea2b18c19778f80ce4cddf9ca8a91ed069e577357d0cfa207fcffd1bdb3513c802dbf53a28f87c3f7b670ab6bc9bcd4fe7476f114d2835ba76fcafa52a6307eae291103531408b8845f48470f8e892474afa91b763c11c44b068c0692c5fea8267f222ff1ccb87be59430f21e14c95ab784486aa328fcabdfd30b0380479d0536e7d99f7c5f9770fa22d3880ffee97235d22ccc2bf1cf112738ae36df4d5fbdbc6b6ef8f462c8853241124da23c4f992c75af2b0bbb32d99f421201e2323077e8f29875600bc32da556e9f7332e156c1908e88730aea051393e6ab9adf5be6b56db3f9875f7a3c4efa5556bd3c02158b10fbc68e39eaec3e629b64beb06e33820fdb6345dccc9064efef70a6b813d3c218affd9c87b28db5b4e6227555c41c80f6c24b41f432bfdb4809dcfda41ba7c3473b6a5915c6ffb18f175f5630048c57fa82191e4f33f5ad3ca4efc355dc756831a8a6eff92a9387f6af609ddc46ba61f68e21a43b3d7a0070f127ab16aa5041d88a8d985fdda88cfdc95ab1d65f86c6d3cce424749c440b6e1398d36b7836ecbdaf7896201e7f4743ac8739cd67fd209acd6303fa6aab658b0f86eff23757e36f3e8cd355fe4bd454e79c7f7e12da2d01377f7ed5204c6d4206643c9638de68cd3c8ae81c679812c4dde7da7d87309046a3d07b80bc844cc9844c627e97c6a1c2a4315e62f639de0869d15e69138b0289b3a197a078aeb254b2dc70f6363a118994a68494be73c44b43e915dbf34592c056a284460a282525b7d2c9f321f17125a4f6bbccec0df391222aefc21eef8edba8ca1f2c60acd1053dff661500713f348a7d22eb26aed965b76cdf26d77e8bf40e991cccc43bbe1b4fcbde94c6226e592a752cd4641555330ef7cf0e93767ce689fc383ccb037cd45a89eb0ee6f1ccbb6accdce4b8c8868423029a17af815dc46cbf3979cda24461ceb2c171a9994cbc459a8ebb25e1effc234bad1b3a12d5c87185c439b05890b7a448615ac61dc4d71d194ab8833db31a00e203285e4b4546096c4a398a6d325d348163a06866e9a0a8d65ae91c4c08d20af0a8d4741f68cabc4a3163286fa1b12bf06c8d5fccb5158ec563f0989998b192b0a9f359604339ef70d23a10b0015787a9410a79a705f78d8e49baabd8d2b1932d7e31902a769fe2330a8ac7edf6fb0420786275ab1fff43add33e7af962279e15efbec1aeeb1253f03c523784d7571c393e9558cf9cea2fd155fc8a8e6a7e546b8a8ec5c320efa0f2308191e3320459d34b76491c51054fd58507624587296fc565b14a72eee69171426e7eb66cd36e390097300ff371c1d1ce99906e0b3841cd63adde61ee01a77ea9eb18034e8bd87f8c1c38b1bf1c3d6f53c55204cf277f0538dead68091181f2be3c8cd83ed172eb6447cb3946ee5e0a532b6310cdb701aedec47a74c71e504b4299f5dc1df3deb2c7907d258265c6061b509a44fed735c81f67246baac062613e04e191632334a350d4c6294f7d7b36842afdea8060a9c711c3dfbbfca0397919270117226859474114207e9973828a23ba9ca3020b40b0bc03818a21fce505d241fb302ea0b058173f508e3447e3badcaa10caa641318df8c57dde9bd397772a58810897702078218496702cb82004977720a86004cb72127021424b7416480182973813a030e24b59c7c73137a602e3ce674b313a0a5a062afc81efb33e1459866015190557ba2ab0badc5d00d0e1c7f9856c53fbb37a26c70376362dc0f2bd127735b6c30ad22aaff40ddbabbeedbccc9795443fb7dbff0982e678449541b1366a6d84e1e5f2cbf28378f09694002f3aceddb418b5021bb4690f1abcafb6febf4f974f1db4c59d3143594e53e37b0883d8eacafd4200450b5947aa8e4581169ab93e19157d6e5cc54a9ef95ba45423138441d7d5eebf60bce70a55e7fa954590f4b416b88acf1fc815ad761dedb20d5cb41abc2ee6dfe27e431686244bbbde88cbdfe08030fbbe678dda5f03ceb27bd9a3476d413951b9b11dfb4185994c371411a17e46f7c24e284d6fb3a02dab1624527e6b3dfd9876f44e2f92d152bf1540bafe7612cddfb1b6d82c47a063c1587497f1c19de1124f9ee2d429f8a3d7471ec3d3702926780c38a32a85da727c3a22200322567ec33f4769e1c01f5b5f38b4bfdf34b582f418e9170f6e9427770307216f22896c1e28b320f10042104bfeef3d53186c03c18009629615be796a7e707ee486fe779eef1481bba275378b6e4407b2841cbd25eb942ce6d5a212b7abeda2fa183d45f72473f9284798c0acc66b24ff39ea5959e11ed5a035239fec0eb698698e5f77e5ac54e8e1feec21631a251ebdf5591acf8b7be23a677f3620a6d6457471014448711c042a836489d793cb387d759ba0a838f4ec212ebda86d09134fa16d323becc08503d4bebc21f9dd60201015ec89a861a7d2991573c703a096d54617f035dbb8f6e1fecb34c480201e9a884f24ffb9046b9f5bd57275e2a79ae5302b4125338f25cbaef6fb0ac2402a6a0495d1ecc97515b6ebb0d8bebb2ae66f3aacfa351fcb466c3c39de13de882731fd432f30d3cc5ea09d869f8e502d0bccb4d1cad8f1cb8892407860f166a28b43373542433acd9147d94e1c122275552d093301bf36f10c99f1aa05555a26d2dc4c13c53b52845b516f899cbb8bd489127e1e2c057901edc8a6300310deb8e51261b3505e8181d281b9fe50de11007530c238ed1092f77e478293a25ebb262e9177ec09b4cc405d3b5335a65d9fa2c3b9c22a754d23ee28cb1ff120a2bc6b68d8df6820949642e7a03d7515ba6f548f23fdc07062bcfed393f3814ecc2c5f864ab28f4f59bce1f3de85d9735360228bf78ce4b22352b82c90af999be8048b68376552af8d8c625f70c93f6dbfdbf3a737bd1a9ad8fc0c56e06b8d4b979c9269fe64913432df7d9f55371b168663264b06250509edce3f3bb2d9215370aaa61f9f9582b44f028f2550c1ca62aa7b51b18173308bb694999572752bd4929b99b217b6cec50a9a3f84bbc54b8d0aa2f89a76f15a538ed50af9f76603a588468048d81543854f5dae376dbdf5ebae2aee6abbae478361ef64b209484fe4aa74c5ff586ee9a4ee02fc60a0963393a3e376ea455adf02aaa4eb56b7f8db27a4f581db4cf9176130aae8ac5707aa63233b4a5e2f5060f39a2861fa82472f941bd0c12a04d5b8c4ec39f31feced748ba965ad23645ba9eb21572f7dae5e5e427d4dc746bcba570b0066047c4226645b19cf9cd92b82c23d05a429c56f183e2f4fef6be6ab091526d396909ba0687622593b8f7ecd9cc903f5e902b218890dcd7413eff79a1c198956b17750db0e7b5ca2d6f5184c12c5f43515f63c78f0b018595737db0e5e93830b45e90bad0f6398c7d3bc5f3ed8d407e8e703029773878b921e4e273448cd2ed340b9451de9dfb653499dcfccf0b47ab0c90241e69fc6ff02e9281b1bc770815c0300652adf2d7d1e834d7395ff0759b9ffa2cd87509ed0982bd1f94dc95da0cc5aeb33921b5709a9cd22407612e4edf463a753c1196d23ecb86512dee5d233fa5670ef51e8f8acfc2a974555599502938e474554fcbd43399346a46b50bce8a03089ea998e76794d0619c6ee89dd4b3d99023d8b991fa687181b33e3b9cb0f1cad7cb04b08d2ea900573355cc0c1a843eff8b10fed288fbe0e3d1eb39e1231feee33acfa35d446a1fe8ffb61d61a16251a5cf41ff4693b6c961da3298f83b7a31f9a299973158b2dfb4a1015746b7031702c36976758f28aa74a59cf97a15648b96022a1e16bf9298943ea51a52752d7ed690e528c678e5ffc048b154f01289ad994f74336c41f4075ba44af667f05ba6b334178efbb3e75357c29816cd6d556928c1713f1573edd990cd536f12b20f1f58cea1ba8a3294e6a05aa2a5453ca8b0d34748db6e4c6fdacc67b6349887a39c078bb8f1c73eec96e0051954d5ac96bcdb1d3e444f499a961bd0234abe02f99ec4a8463d7aeca5a4b20f07ef067a2807b0503a1cbb4dc68bdd012cbfcd6a64b25e9b5c22420377d52b37d8030292b5aae005217f57ac3e565df1fd4c0481406c04e1a9b498e7f19823ff04a33fe61982ecb2e00a80661cd63b1b771b9286d3726b150c92f732fbdf752bdacf15e282e066eef8e397fe74b22ac20ebb0218bd0b9b85f08d4778de0029f81661e9222b1b2bc369ba5a0a04499c05441bcca29ff31a8db711584ba8bbe92d850803ca62468b5e724347a931e70d7b0549f24f6158ee41888bdf64a62cab71f22e3461f832fe851628626a89417591d98b321bd038007bc6749223ba3566e0f6eb3d6351a6bbd7ade97a0ffc34c8190edf12620053ffe0182fd0f9b9630f22c20505dc817455e4236f1e359f5f2e01a2d50bdaa12114621f0c2bb407b32675b2835cd619eb76aa9f32b73f1204f77ca7180ba29031d16f704a0a69a54999f9161bfd0dd893eddb01977121c4bd135b3fb65672eb8d49e7e3c6665788d36c3204251d8c4e74eabb7d315e51d16758562ba5b0e72957440b7484e7747a9c01a2069ced6df7e3275cfe955732c08e02f442961c8939fa75b6594a5587aafe43969cfdefbe3e53beb640a82ad6500ca595681074b1faa5aca68e489aee2d3a553bb66d7d0c9740241fd975f0c0c93e7879dea982a5d338443c2a4d11c9363b121559181702820b0d78d35104624098e243d8213d97edde0d8d0b5ff4730ccb2208d01ac827362a027f47a1db72be449fbc0ee430b0065403da0031a37fb6560af3aef95a10702c386c8e970102dd2829fc44ea11e4022ed66917845c2b928a005e8594b99a25fe3485fb6bcf940d06930849b9ffa8a8dfa8d39b5f6554db06cac1dc51fcc3396cc5700e623591d148e0c29d63d49553bf1ee40f406c036456f2e7c5a20214c881d86c3dbe757d4f7600da967b5038330327d0f42ef24236fb2c166e2e95f9321340d4fc7879869cc790ac0599c6d310a8aceb5fba8fba0810176f9afa16ced22cd184eb488a13fc454f00d87791631f0973ff9077e90efed9a841c0e82a0a3ff0919ccfd28ff4401452cd5b72fe0206b2e077a4be5cee564f660b24a7a3277a00867b3dee46e4475b2e94b571ca1d952997ea9eaa44dc28d0cbdc175348abe89c930b84c935ec900458d6aa3f6a7b7db770204a7185642d6ed43538cb3e41fb1f8f66fdcb7b99e7a6d749909f557d459e256c09a7dccc3aceeeaea03d5bac2a765b89705e8c4b8fc17d21415bec10b711b98c7f425bb53e4d65c38b3753c2166b4b2ebc1a0c5c7fad0509c8a307239dd64a62dc8640298bbc9f9f23ab7069b184f0f718edd1cd071d537ee0eea96028169908491d217357f6be91080a0edabec2778fc407b736074f21092c078354bf16d9180b9b69f1a75c9c39e913b26471b85deac8c8c64c7251c56633f21a0c7a0b910024694dbf025d4d5d30999c3ff2616fb927e28ba3cdec796e9c69438d2a0c76251015fbb57f081b5e1658fe5efdefe1d39b47d7bc67efa352ae10007f7e4d73ab65e31d99edffcdc93f677c99439217293e476b5f25712ac0ece21aa21eb6664a1f16fc2e4aac4ed4a74a714726d730aa727b7f5d3c7701d6e634f9752e3a5b05fc1060e5bc076f777dc2c9d6978f97cbc5f9c89ee8a6c26f2f4ab39934b2faba88fdd42bb3ed62c3d224858b4e840b8aa0db90013c6ee74b68f3eb01be6d735f3dc5c37d7d6d6f08dfa59562a66426d4afba09edbafca4b0e17a8205e0f4d64f6273b321141ef40980929682738cd6ce19025afa518725af80d313846f72ac153dc210fb2c2b20169b36cdeba174fb418c2b1aae396e6481ca842c4f3578d017cc55df825b5c6f811b3c6c700427f255e69de9368f2c8ce69d76c7ddb0dcf4e6fb7707bda8b764764b912681f1a04a010448dbc31202cc8f14ff1e96aaa93aba3ef8deb7036f74722cb5b0a60e226491e000736049857a6316734f727efed7eb2c2cac4b2601c384a71eaf3cd08ace37b578cf8a54aa3a371d5d585592c30af835ba0c61c6309f7b3de298c95b2acc8f0be081fc12e5ca01049b41ee9109a52e758fae18be8240902c44816db77c1118f613052e58c0946d686ad0ab3929251ca7adbb4581a63977202ed0c7c7e32a4821baa6f019d40fd1448111df6bf4b87b30509de9215a1d9597d936efc0d63bb06b3d30af1d281674c69a5236b0fbea6e323be4516140cee2bd2f93d1a4a1ee646075bd2c0e5b035d125837bd74f762e9bb95360d23dd9fcf12338a709f013db4517a2471ffacea13ddf6fa9a842013849e8751b7879363ae9f2dae46becf750d6dd23fa04b58a6e0088ce0d7d492c094aeac290a5fe55d4e51904751b0e7ce1ef935483bbb04ce4c3290560a2aa6a3d240ab930b1c9d7054312ca6b02b93b6b4cf2ba637275dd52f16b2c601ce3ac17844125a03d2ecd083118910848f81e6348fa861c526943246d068bde233e0648c5a0f3b937bc11b82c0bd749219f87f099bbbe3a4473ed3f7c74892b94d91e82fe61b84f4e8bd60279b4e138d2b1bf58eb06a533a333d46d360b07055fff29373729124e07a77099c70ef4abb2929a5f016984478b4929b3b0a09bf1cbe1e90c1d4aea9b07b8198fb560a92b204913b5bef608577831d4932ce426b5810a24ba33f6d6c09230a8adf8565da4907c9680073a56f95ca490ea5944742732dc00348c3c9ee8dc3c3d39adc6aabcbe7c103c0e85f650a6395c3ce0fb7d98e6908147374a23199cc5f350501890c7153e2c3d65f51769ee44e1daff63d023f2da42c0f3a890dd11ed17c00a67fc7a24ebbfa6050bc140d84b00ab6b2facb0e0c38dcfbe911324762489b05ea25341072309955abccf0e3a485c1c12c1c68acc7824de8716c2534f9b1589272b3acd21fe467b01857bdb10104a578cf91562fcb1c8eaf7ff0e558f4d0f4edd321523a12201b7adddec67c719a77c37abd9e49c1185a5f580c825b90809151c5fa7512fe8abe99d62faaed54986dace821021df2f020f452d4547b3e74074928c9830414bed572e58c07ed79f48dcb2b64bd4013a137985d7885bb0b0c88840b56db3a4eda92fafb9ca34ae40329aac638498d97ef22a188410ee6964b0af657f9b7285f2dddf39046312574f2c0d941bd0c237dd99b6db8df22e4706814648571ab9553a13367626f3ac16fae5d3990d512a291a3f3bec41ba4f6c05e7b6624661514cc6d51206c3982a87e921f6aaa91cf3c60408c33aa58b0c393d4c88e95df3624c89e30a9426a80af4df671a50ce82bb513deb970c240e78d74d0b4d670b9f3de6c3dd611d87f203b555f1035ded001905ac7c9aed03a9addf627254c4d75962e892a017bdf3a8afd84424a51124047c20eed9df0cb62318306e926b40bc7888e2e48accb2192978a9f86e390c69ef12d8b2b6fe3d09dc1608894d5c92f868c674a228e6c35ea1423bba95a1970821bcda1f44549520d00c16a16065a70034209d702aec698a8e6fe1a39bb407d2f5009524d6035b132a1d297d5c1e895ace4379fc7fc5762bcd1e1d8361bcf003a17cbc131a5cc38c418636c23908f079f68e6dcac892dad3966cb872c649c895392e5e3b42f6dfa9896726ecf891b68fd84c759a181be6587f8c06253166f370869c9586aabb8b932b74e0047a0af31b09afcaff9c3735754d85f04a5c53c5cf350c69c233fcad718491c5cc76c5260e9b441cb93d24a864b39bddf4f79222ae8ac6c5f617d864a85d7025add93b9b5dd276197d8a8c3675dce36334cb45046adc85b3718bfd6e3670c28deb773b0582684bd80b56e9edab17493eeb9affec4caf364a46161e9eab22c0c761ea0c8f7bbea0e393bfb7761786abb00f228a885bdda24457289545aebc7876c751ca4483ae153d4f003184786e806229320248b7b9b5145733043cf727ba44f2fb9b1cf90e3e7f11f0e38adbe160ab0f983371a6b4ad8d5dd68d3b50f534525d7c101b121f691063c0184c94a2e223a809620df181348e4fcefe11e9ac9e4384d2a8e382c4fa4e6b34b0732c573b25a1e2346c334e3815dc91675196227e6a743e4dbb145baa1061ccc72a5626ceb6e7153b4022fcd956e9c6cd5c966c904814e9ee1eb6ae0d5c2acf6b07d6a6da08e27346f4f2e6d11c73e5c8d503b8961632fc91345eddf709db4c227e2a906b58c84565a2af656e7ee4a26b2a9c31fef0c45db527172e05ce974f65eff85886f53b5479f6fd960ec7bb8ecb5f8b9c51024c7fb7517751d57ac9afad0e004cdf4e89b1aa78bc8c602127097b33201c4f46852bd6c3ea74ae9a9bfd368d8a3a8c52fcc7b2fda9ab71ea5406ad8e7745bfa8357dd4b4f68b486abaa4bf83b36d0dd6359b0694e07f8315c7028fc8da820be91fb6f294fe4850867c3b5816511da071f84de56c93ef088541732aede7e68933894ceb8bc9f80dfc414bb31bf9cb9d2d6e669a38d2a1617fba2dfc3b4e8b195f7b8d98f16f8d0bda87a6a2f5f542a9ad921800eb018be9e8b2f5b0bb670b3d33fe13f756d9d8675491c3ec5dd93b76ca0aa606ff0d7c07796e4c400d5b2afa547f36c8eec694f6c5d9a707592c0747ef2be1b9fd24e51f66f17b98557195131d8e5b6ac4d3b0a0731f5dc61d65348c9874c9f3bf7bd6c158a36d3fc86a1328031f4f8c01844adab6a075ff70eaf78bc4f74c42fd03d353f0d2e9b9a66ee0dd8f7995f0b5466004a104ca517a989c3e72befc41823ba151fca147c9b05417bb5cbfc9c04031bb696d26af4cdc4456ca1130d5be0fd41e5131be221dbf0f2546fb71b8e48538b70fdc048acc473658b5cc1a2beeec834cbcc6523269db062c6deb7f82a3d66ea5eb8a0fee506cdc63570bff1462d00b5957a6505889a047bca3cf5413ca11eabe8165823e6e211c6270038fddb12935787876e826408fceaf411e9af29461f03cd58d05083f68fba252a20dcf1eab0328605be9966dc7ac2009626612c9214154e72b3adbe2e3b3efa395eb4946f15222c027daf48793784cf1db60f55243a0f16f5ae58eed90cdb3f7ff989efd58c5a19bd0e9685e6b9dcb4d3f1d49f8c540fc8905b631446c04087014754c6f4663378b0e20d8139a712968c6b1569498ceac02785421e43e0c65df56853ca868945720e9447ffe785185e4f1eb699847ea66d9ec77c8d8eff970ba32a843c55a2c4ce33e7facc3c6508d965593b3db7b619a93787fcb80adbe4236c318dc2634b2f2c0e769c53ef5070837f3be65be9df22478105107cc7ae1993113bae798e2afe622143e825c03e1c3d7518ddcf8c185ecdbf21c739a1b573a6de951f9f5ffe294d033a1429439ee969242ea9432ac7e57d04cb2146d04be31b47d1381e45e30ecaae28e25c2cd7c2754ea954a27f04288b8974ee1941682e8f6666e2e351ddc0f3fd2f601d51dbe677b1062a44df0f0945f3de6c44f6f8fb85418662d144bcc50bd06345b8fbebe16aee5d951a4e1d7e1db09cdc13540d1091a77ee841a3938495687cbb5def8d57c21c6a1fdc2e7eddec31698baef567646cdf7792033a6747ccf9ce888c6ae70b897e4a4197a36cb94bfa9c1dbc16c2056a0b2eda1dcf7817d1fe0b80522dd65e3968a7a43e09d6f9b1f8c15cd7a4e8405ae8bdd107d7e0e060c34199a77ff476320e3ff0eb0a32c71a080bfd915c562c57b0622f73c9c53169fc7c55f13e50811530f499d75d2d7499274bf6c0142538845af5545061a77b28b9942251e7899bf3b5db084b28a98503688a3670a02132484068333100122cadd6140d5ccd4e3d2c549bd4da318ab78bab84610c1977e6fb7126ad765aff2c6869d767966990c6e383a150b4b5294a55c299b31b1a9b8c5dc3cd04d453d0a1a98f3baf192ff18bcd57ba5ec65b35ace851fe0480f480df20c33e63022333d609574fe2054600f006aa6a6ddb3fd6e8f863b6d6a1955a1cfb980f34e0e1561a97349e1a7e20868e40228f505bcb061fbf97e152bd3d0e79c2d346c26f9c67f4ec5dd51cc6ce93ba5f3ebff4fa543ec537e2ca5a64a4c044c0881f44049bf228705a3e4688a4aa39b8fc9e1dfdec5467ce7a9b29afc47cb439fad83563448582f62a8212dd453edc1cf749396feb2c723495befea97cbee232cd29215c39c7db67a5e0fe7217f1a9d22a212dabf2f9cdc282a5eca33c5208edbc6d2be50851a3ce6e5cb8892f585e703c1760cd1624e374f9bf4aead66c80c028c508bfac188ab2fdc6617e40ddfcb5c5121dc300fed97dfcb6b7c9de2005725c9671e41b30a22f1bd632eef35d403c4a658dfa66ef308b01d9de64e3d330e8614eff52e3bb463cbb9e1c1290e01f50a0f6c9a4b9b871e01693988c84ee2dfebcfa17bae200c55cb1c3aec20522db8b8667ac11569ef8c002902c734bcb72b3329d2e630dea1b22f638350100d292439a87874c8d215b0d5207d54d88388caf64da55d648b39270188a68bf5c6ef134edfbac99865b5169499f01a400cf38843dcdc5991a56db7a33422c50db0532207dc548b4b0f2fbd68b9f795a66307c43fcd9c2973bc8bb4f48f83034f7643e7cd0e1b3ef0c24663caf6b586927ce434fda72e13c54bda6fb9e644353c7a54cf4fddbc88d58105dac009b7b78b3c82d9611ee22199113646303bb42063f409c796440202100b4f307d495856d9ba495e64987d662dabd9bfa874abde248228b818ada41952bbf294c4662e046887c4f7e9b452be342b8c39a327b5c059ff8623016e19a14f10d66d8b59901bb2917fc5e48ddc2f64306f20eb8b99c0a51a2e55251064b224a038a91f89ac1fafae16edb40699ec1fa14a2ce212ef529e266cd3f2db5410648fc6946b34de480c10779177126f88e3e8f7f78790f69e0f23ab98355c7438fce719c1aceedc2c2af95ab5130417d91c06d701c1d0006d6fb225bea3774a10e3162a9254061ba940b30f4015a268872362c600f1efb4a96cf46a01f071a3f4d2516b2674e4cbd720ffe01c91a0bc262465ba8b1ff0d239edcf105bfcddbb628ebbc391045833de00842d8712a7c6e0059d5cd39c93cc7d47f70cfa4a91e42cf3d0c7c28aa11c3fd9e1c5308ec864fd1fb9457c6c88cbefbf2622915da1aa0bdca5853b119c9236d5f398613dfef021e7f27c888f37c701953a84e43c47982bd6d84197f85840ca1734ebb3573890601a5c913b8b98fcb02d5a045f4582976ce25bc8aae52cb6927b40499edc3be5031c6dd76af2e38ffe0e489fbf0cabbb0dc113f5b010f952060f9f5d54016b099fb721feb1e9c5b3071f205e26d26d98d7cba57c7842a4c91f89cb3ec42dbfebf418213dd8383ea44b1c1a319e8a72a66af6d5bb5c4a130f5434f63bfc3a9bfcd2bf5068005d49678c26e7e25182357b2aac71837b3c11804f661cce2b82b9a52616282ad4fc37fbb949e5168fc400dfbc49d4496cf88fb29129cd2f24e37b753f5e737d57a344f37c208ac4166224ced6cb0d8afa16330abc7baa491be6cd3638d26f5cabba1e0d2c47e2783f66233ee793ef230e5adaac0393195300bd61c081e235ff2f570b667639f446f18200d5a23dc7c0fdaf77e53a43b5161e279d29d5f6bfd0a7df3170579987c5a44c5b5080e53c90c03c038f446d4c577db173fa7ecc1bad96f775bffef1286aa016878cfe1831e26473358737d006980600d2c8408a151765b382556a3dc8343f632d9c41c28291d880a717ced71dd3182e1796e0dbb337e6fcf702d8180bf2ab1d63bfdfe390abe0027b8f12a221769d052de407e65706c34d52099b3c24d657834606860de7c1e79ec6a3499565062f47b14340974df61ae7c786299d185a940ffba7e53decbfd2109a7e0a4327fc15985eac46eb7932557cc1412eef1e0c892a1ed0e9bea1604bc100c8979ab0f6a6400c5a017b4ab13023f773579850e56ad647923627f8b7afaa97c1728e46b1a8667079192e7d8228af2bd724e23dec8ab9eb230bb35c1733c3091c871b17e63bd6442b16e4eabb088660374cfae91c16fd78a6d755c162ca9005ae94411ba83220f4712cb05870aa5c675e0a2dfc2d634bfd351f0bc5cbda4d3fe5aa5d6f327c05cc449276c31fd004ec229f21f2d1c2908b80fd1a05148676321ea3315913a8ffc6c74c2821a9379fcce1627084c2577c677dba8c0fc9bfda81deb04dbf72721e149404869a0a2bdbed7acfc58aaec758e383ddedb2149ba4aa5bfa2c79d729ecf032410498d6655e2a4495447c3df2ec8e20ffea381b748605dc340976f4723a91b66cb576cee6ceb29b87fdfd08dc200123fcf1e8ae82bd80f2e1726c8a1ed269b37af1655534fbb90ee37c54ebf119d7564959762b0663b48776c30866d1daf9d4fb3fc937d222dd711b8694de2e18ff70893337b183bfaa2230419114cc02838d1e5548ab3fa28d569e069320f7f50f21c7b852054dc9c7da2dc6df57e9e1ae9285e2807210eceeafc8116ea5ea22bdb524c9795c76b5a5d4796e39e9345f2777ae26f0d0f3cbc032a27e82b582b702014b9be4e366aef9fc2c9de6b38fded17b140fe72ac104dbea7dac5be501c227e3f2745231666b204fbb9d72188da94e0550dc934e73d08ca7dba3f77b21e84eb0fa3fc11786ce85a0a3341d707d30ccbd7840840a41f71887513c65790f0bc7e3ad367cf222cecb6da2f21bff2dab72896c95bbefea77e09034380f2e98f16c3f9afb29e138e6eadf8d3b528186d9cd3b4c1ffbdee07141b91ea724f9a45c5676bb04a374289c541f2ab9dbe92a8522f99f0c3f9d066ce7bf14d45a01e91c5572a14c706a056a86557747217228f0de11154c09ce7b74089e5a2adfcd7b51b108b9b08fa82ac5157f618270802645ec7e6a9222dffe66ddeede77ce832a2bf33544d8ef9c940085e2e54d6fe3c682e57bfc2214158ff0a48aae6a9cfadde0b804da9252e26102c43b31e5e00e3ada5134450809ccef66c83da4ab5284d9e7eda661dbddc46641833019278b7041f94ddb8d25f9e66ecf1c63c9118e960ff7992759c1e7174b07c14e3633e48b41104b5c180d4b5bd108b8db8311faea3917bef201ef38d917696f4f3243d7f1f638ee8b42f0bccc3302a84ef0ee4c42b281d7b9a176cebc0bcce4f26e3ffb1fd488c3018451e7ad143d3233a43a1182d32f762295f4f57ccb2b9ee7d4a1bc3a5059a438de282240357f609b9b2acb3ba4f17a601d90849802e25a71e639c1f98a21b19578907391f9274c73e9a7f0c43079825ae2fe02ad20ea404cea5c5d98aa46e69151b94428e1c90aba1c77c5592d265499e343e4dcc93a2d62caf598149c5fab7893aa85db4c70dccf9d5399dcdf22e43a80f8d0425b2b13a440e6304ea57e46e83c31e5961ce8532bffe671c280904c4923da20095e3066422c77d551912257fbebc687b10dc0acff92b5587b341ec7bbbd512f0a4fc6a25be8bd04eeeea44c12ff2d7e618023f61229737a0cb5382f80835a91237eba104080296cceee3f50f6fd66a52258a29496e15fcc92911339aa0e9019405f10c3dc4a6ec9ea26a06ab0762d3083607a66e6478737bd50ce2dadf7046ab07075daabebf9e979b8d63298037902e135cf75fd39dbb3dfc366380649ce334a7ec2122602e644a8bda05b7d782a52ac1aa997b73bb9cfce64875ae4429acaa03b511620d3be0deb348ea6a3f4bb846451d34fc84597da70e8d90c2b840d5216e8e35de5c5748d9302d1afddcea9b8a69cf10ab5386dbc1e5d4637e9b1a906fc13cccb8f4dff3c8b8c301e26f30c925b28655de76a66edd3f8115beedc0c8445bd9715e78d1c67e72e28d0ca0fa513fa36d5d3898d213782ccd8856bd07f99bb055072e260b1b5c0ec4a5dc638ab5042ceb357ae95412d2c35ae754d7f0faaf75c06261b333f8ca2dd211407a329263fefd93ecea965f1d65064924603fd19d4935905cabeeeeaf51817756940a08f747bcf7a519cd7e527ea68a8c13b853f14148871915e979d87d895d0d72dd6bd4d2cd344e4e30414ea532c7b34137a9acd4eec7cfe07fb2ee2285b9c2661f302251f46fcb9b9c6d529453ecfc62ff35f15a3ebabf232bf980380456e23b1e14357d5b3bcfcfac1eca6a228e54d8f514947b6d98c549bb2785db3211b816324fe0d287afb67dc4398ec4f4b0350f6fb891efc4563b105080ca1f90e4c21cac6600d0dadb0e8b53f5ee839224a2102c67bb7cb0d01012324bdb95d2b0ea550ff6bd2c7027df16ea22a2c27ccb131029d60df1f8c4fd1935ccb7f8d26669f608c762af7fdba8942d461026226c35c69932d0d20fa1410646312cf0bbe173fa226d4f20b24bd051e5df8663b594dd90c6134acef7705b4286e80d082fd85f4fc092545f179c5e7bcd9b057372ebd2743828cd93ebc9596b9a8c03ccb9b5b7aed65b9d30e6f784b1341ce4ca24a341763b6d4de3abd28477a08605d9a8cc63cdc2850cfdb61b0394367fcc63e5a5f6932564cef2d75019cd409d8d0d60c46d90924aa205fa701e41d86619513f84e563adbaddd5df075de13fae42d942dfb85f8ba84fd9c023f19835504a581564cdfaca4cd2a098fe1fe6733dba5d62809b414aa981199ff68a2649a54cf2ae06c5a69519d6270ab372e2a05c40ad45f6dcbaacef477fea0d01691fb7fa969038e34fb0328142fd97bc368bea811aa4a4adcd3b3b9f2d0e9ac9d504dab3491145c26a6f8f941595001040790fe5522e51351744aa0abc5de5c22f80c4cbc60592fcd098cda36bd4b1ba9f2d26ee20814a8c3d894bc9aa360277d40a11e72062f935acfca2294f34a405b1808e7c302967ba83df743958d7c2ded7df05e6d2e17d92c013fda763577ac42d54e32bb90439c156855162f0924d816b529950afda5cab714ca06da01cdd990b51529c129dcda9d5db18d3e4d1ee19f4a794eb295eb30127e45a18db44b23918fb32ae137ff7bbff18491215a39a44f88ff0ec8ca8bd03ae4d110c237c7efd869e023b0468138dd2630782c3a4f960447f5901f800473aa15befd0a3b6e92b8346b2d12d3188c9adda3146d825bf39b974b80178c4b30cbf675a9a6c51eb9b2e9ecbff24da4de2a18f8cf26131455e264ad0afeaed7e3d63c9d0e4a537edc3660319b0aefadc45971b8cf8407310d85a4a7fcc5cee1cc919cf49aebd9c0353799ef96cb64d42566e80a62d5f1474689213da713a8223cb9be704204a36b86c7f393f6a871ba7b058c3efd075cc973cf5b7054ee9ca9544216a4e8f4b1f37e0204dbfce47c72efc13d5088666d7cf4f7848bec2cb8935f1fa1bfa1bc334c54288b8ec834a31918ab89519e352f71a014ed7e0462e62530b808147122fe046cfa6c808d6377a0f44292716c5460d6560015be3576c8f6725048d76b65b32274fcb36eabd73ab38f177644a787017637426ab74636163be4d2da4a8ca0a35619039c4d9a866fa9f5f29b425ca05e568033ef4b835584c647ce602274934bae0fee0a55da70e9bfda5e9165f8c4e60a11a65b2671049cc3d6a41574d214e4ad7a5b8223d3c942f2c97dce01090258af347606e3bd455eaf4c6bc73b16ca5b9ac11694aeb9bae7016536c52ec57458d3ac1a4e246be6651f2536c0edfa74d41c69c9c40451d55a422077ff0f34f683352c1ef114bfc40f804c7934740ba27e57f017957649be386a0034ef87ad0308d51f06a6ddc3e8947ed70be7c2a3ed59ccb8d05768e98f185ca2821c5b16195c9901843273f8ce49278db5ecc10ab00320f70181873dfa1f457bf87633b60a1297b6f3a979624664ec60e9f53e775d0a08f58248f8518dc7a6c4a1d8a04bbb4bfce856ad2ae65b13591eec68ec9f165701143e3bc3ad19df6d335cec6fdfd737bf631b6b35144e84a4948c70d7968d3a8e76620ca552df23408a6e01b0aea5ce52b0be74f5e104a9a723031613e1af9b38aa797b9bd71eaf31c4de46504d5997185a4bac403bcb1bfd511df74c3d344b02eaf655d3cbb966ae179c0bbd83e95ebc3b6372e8d9e61106afa1cb544d83d1e5b90c374337993c2f0cc68eaca5420285a9f481e97e502e98b51a5598d68869af63a144c82617648bd1aa36ea7de8164ffc764ea60c6e15cd7d4a8e8bbfc1c7fd46ebd53ea28cded1388adb08537ceca79b80d906507b7e429b2ffbac27705a89a2402422f593c3edbff647ec1786084e10e0008ee221244d1843564ad6aec99ce723bfb228f304653087da4673e6611ec80380c828385179ca57ce15080b5f6e286ee995cf5fd601ba42b806f441c395acecd701a024b4d655ff613846127c97e8dc08f1e91465470ba8ffe2ff342dd256604efddb430148dc9d34ae33a0dba9175aad321ecde00342dd0d3bf5c9c29600c319995f1cb20ad5376d8eed98dc9e9d193927f4542104a8fb2e9baa40bd03c149cee2631b272dfa829eaeb2f2103020f018d4a4d24a8a7910b9a2da75469164f6026c544a58aaa55f4c297bee188d45c0bb0f1d587ecc51652d59a7fef8177d3957c4845cfae9dd83fcebe260c04a10aa687684760f8447756da2c66b0123292e4c26753c645b1f913bef396321e1ec73c0b7ffde6caa43708d7f3aa79589a902eab3b5bd183330126c201d5d4aa82548b8113b4c546f248e44e269bbcf83ae8f35808744b08c2e525409fc17373aa63538cfab8e41975228a9b3fe0aa2208e05355e0b254464ae2ac7dd95ef2353fb83343e5ced6369386652c03c22dd869d392096ae072dfced3708e773bd3fa83021684efb0caccce1aa512be51a5afc675432224f99a14f398215c9bb502875b14644bc82229d906996e4b51f20972cf9ec9c653a48dbd22512b3f22595244350778bb959270c3e3316f5fe50b429254b0bdc41036e0b8313f2bfc10988796209a3f9401626a0684b7a6250c964454fb603b64e2f5c4ca1c4ddcf4cf765c2a9f036d3623895b0f48df440004149f3ad1f66f335d47b6c7a342a4c1b5f1a27ddf2f994754bc853971d1117689854e0d7d1f6905d384fcf31609a948dfbac92d765b469d76f7030c04b88163d334ff0900c0739fc860739ac8aaa89c8735ea6e8327dde6ec5bcc81ccc49226a655944d60e1b0d27b75639feffe31971c4279927efb37466d590d43ce8487e065a5f65280a198579cd34404f8ea146459ed1339148975f9016c93203975ccd88aaf0ea1aafd2c6f20b97ca4ad7302833ec7b1269c2054f398d184f0d356a3889ec642335b52c7e126e3a7d582bdbbf473d94532061001915aaf55672a5eef01508d1c0a445507a77a01a591a94b9760ffb27f5b7a3e4d3114159e25fe2df2263d81a906a3a341842b724027488c94743ae92ee4dcf8610867c2b8552827ade70a38cefcbd48b2e9d210eaa89452be404ec8bf1b03b950da944f2de417b3c5174f53e350c4a40fd9065f25e7cf907d2b35b1b0094a6be7667bdef571e281dc99d52a0703dd212ee81f0055c6f187c9a90492a89ac7cfe74fb93817b5625a9a6e19ac2884982447b378f783ca02e0bdd1f704af817de55771efb4d8b95e7ed11a8d0a292419fec44adf6386b6eaefb600a5e8b627606f9e5bfcc9393ce3241610e20425c46e3a14f47c1c88868fc16ef7185ccf53667c6ed663cfd4874fdeeb915fafcfc9c506aa4efb542693fd82f89f6d50b95f57d7614f118d5a1dc727a88673f18d670303e91adf6b7b6f766d3e92561616182880d4f4eb46d7bc79fd5920c69b176bcc08a4b7a0f7f76cf9295c6d77f2f48ea82f60ec99a1f784e80932c05a3d8357a63fc17880b3e395e926774cfb0c239e38f1a11e9cf548332d71aa9c7a652afa63b80c50d45709506d5e97d81535f9145f0e1f586e95d83f7013168ab26f7a6bcdb66e4b9791ea7ceff88130b4864cd2e0d926c4d771bc852549fa066f8814465578fc1e51e51170c3536910e1bbf55f410e50808e7f584464eb9811c8a112c76509d33917088806f6a808db3146c8c636fb7a41a368e49a9c9b20b381609955e64cdd5b6a6664643a2336c412340a264ba649b9cd5a85030203aec97691428826d514fc99392eb9b3330f0e9fcb5035dcbbaff7c73675e3de953aad51029d92ada5095845bce3d9eacd7899480e597fe94797061051f77f59108a9d2dc9467882fce8a7b7f620185e1955ec8e6697cd8d68ea07b89a519c3b00a831392c4cf3ea12661e229a1a024c2446ad1e28d606d60cf8a37f3701439f3d524e6e1c45982e26541e3391633b09eb38065fc48329a6616f53ddc31be07e4b22e58fb56768a9185c66f63a4585485c11765519e3c13437e1884e00e90b645c67a0ca9b55d897e118d0060206334948958d23a474da932349187f5040ab188ec201199450e3c96ad613109138d38cace988fe1f4d95400b838e539fb010195a1eaeb721026c46a613c73f2ceef26e97160e59e5b19c236d99ec8e5975c42c7e06823053c6d2e81136c9ee59802164ef47b01444e4489e2e0e7406554c587c6748791ca627318581adb2bc60605ac75eb7896dc7a96e1f8f8675672b584ab49ca1e90509195aaad2aa8b058e73ed40e048ac808b9e9048b8bae4cb6431329b8f4c7d0805d0b8509d84041668208e5556db0bfb04a7a656751c522407d47435a2f63f1a4e31b1f5dd4f2257e144a508b344777f440f4d7097fe277628fca239548765bef5e26441015be6474dfce3221227418fba8cba5d45c87764021ad4dac1f9971c5ac7a20f0d7c302166c3fbbdcbe5b781150e1837e2ca0ec86fce1a8df06052a008a0c511e6c8e703083c885a4b1d221a5d46616c644ad84cacac3881d1367f0406e8ec08480e88e897e5954f1444aefdaf7aa9706a3ca88d35ab75d5b7d0d9a3a7d558501eabb643b168866b00d4896751da9906f23d85b2c67e84a837df2074a20542c7dca0d4c9d3bbefaa6ee4ff6320b34890b0a6d69846e75af0ef9b52fe6cfdebb8ff335684fe852c331d21cc5a6ab8a1cb3416fecea086c918c10900396021b534a957a5d4641a9ee6f20991a3e9e23ffad980137a217844156ce044205fb07d6a3d01822b1dbf027a21ebc885e2aa3b69dca28bf4e484999ed834c9020b960c2798fa1e1a31de1c5ec23ce91869aade45505186c1508ada351b4b684f4b62bf7c071086231e04853b220cc33000420db58ebccfa79ed8ba09f9d4d26a128b5cc28c354e02eddd798e8ee50e4f9e265551879495ab8b2d32632c6e775d379a15c261e80e9b3de36dc8095e3264ce5654d4849ced52b76b12379bb647e2f1fcf0ee7843581012d91abb718af7fa63cc2c491892136b07f94aca2547af9c0dfc070dc7729bd7abb539403e1b55f7b23d187c97719dcc4f51d1efa3c1c207006dcaf437d38b2f75d068b98004a4280f560ae17480c43f7305a2f83f97eb8f37167d335add809c46bfb9efab0d250f0c51b87d19583a58ded802115333ad0f17492463a0b198ad6ddaa6e04a48279094f4888208ae7cdf397fbcf7534a2b0752b801f472a43d82264ef04b5f2cb8cc9e12e1f9d911e0a370a4891f1ee4eb891487377dc27a997e03e9f8d52fbea80a9d4455d4cf4c69ca29f67d3ad9533f88d63266ba20a405b8fd163f6598464031f1aee119392b254837abd78876e26ddcf2ff8aa8840001ccb09a9737fbea57d7c9c97f3a380508fbc8e38dd827a79007aa97631badc34ca81c64b5b7e165700e27381eb9b599a311e723a5e3286243265e81a00ab0e9a2f72446134a9b107e19ed5818d32dfead2b5b7d574204e8c0f825521e0153e6cf98fa25f859c68f30f4ddf0a9c85a971ca4a16a98635cc7aa64ed49b3c82aea0a02a6d8f3c79aff2cd4b384d648b8ea80ea216be3d6937014435f6fc2b8b9c274982b59f66c42a0d5c3088571849fc2c9cf71d0e8b23bde49b4be43665b893c7531e3607259c9020c1e06987515b27ccaecfae6831e38d71e350f9fbe545c5fc7baba9645453c806cfea68aa9ffd59d6b89fcd69d5d6f7f5b6fec73df2ae1c4f4bd2e6a7bade0ff82578dd515fdf529d115c17da9a29914a9f12b8905f68adb925165ad1834870fd6d57ea8c4507520dcd37f74e17384e0c23ba4af9a8e95b98f496060f94e0913f90a45e3fa625f430c73c3f8b9519af46bd0587e04be8b648cace4441e381443cea8fb8e4445d6be349e9583af24244be9429ed45092d1394dc9739f39ff9684982ce9f82d971df0a82bf93792d8f058655ddbcd19e34b32b65f2235b27d38ca4ff831b343a1cf94227eb4e4caee56d4d8d4204656df75472eea2dd5fb1ccfc24014d16d06ce508df999325d1446d95cec2582544d49954104a74b30bbb9cc086e55fe54fccf5e4455dfff724b9aa09e81dcf92c85f93b6d6d0b219ab3a2d2961487d261cc4caeff3ec479de07f9f441537edd3199427ca5d785f4d3d0a4b7a97e186ceffe636fe32096d576644787c170f9c1a312b39bbde42e45fedc8ad16529664e5ca4b3be4a12ebca04a7c40c4c5a4942c77a57929b416b208d29944196e45e636079d80c5e24f9203480336d16c98c026a74843156ec155c4c70004b51ad5fea6ba973b1795f64ca5381858fc6a16ab7553371a8577e6035ad91f06ef2768d48fac60a92da599b3a6ab04d27b60a9562e2efe5a5b0e4a5f9195393b7ddb41c5f6452e442c9ccdebe2b8f0f490f0b69839b3a3ca1893cb6292d38a8de6894c3a2b64916d50e91bfdaaff91af70550d483c1d2fada91ca091a740bbb46d8471d5969a8e5b57b5bb95b7214be301a3dd744c85a4285d9f6083abfc2169a4a2e38aad5d42ea95d9fee5897a767594f8e853106810544b511d3cf24a34f361514fd212b9e040da536de99a5d7c9c368624dec876b276dba8add6c78932b703efee1e026327a2b54b2ff21959884ab1ab97d87cc68937da12f45e3e9c05a47bbe51f4ad2c0bec41ddb43ed71839a4686944bed90e8b31cd05135338c06108a26e6c384a22ee387dc808a5df58542adfd4b333fbdea2399bb48d5be0c17b1c794f724689b5fb986e3e154fb2957a8dbd8a2f812c27e1d36877238e76b6813dc60d414b6f48b880e39531b41287c926d153021691028d30b1a461747235f048c7d6c1b5073a7f33bd049d5077cb27f3ccd74dacc76c081110d9433745cfc6e1f8eb5729e657eeccabdcd58bbc76213a8e32e9951db9b388ee394762bd809a3ea434623d52c3da6ad8df18dee2623d1247595733e0860c0f7b7b5048c6d2626adb059ccbcbd6eac9b7a57108a91dc3184ddd01c8a8c84b830b8463ba017a1a40f3e43435cd4f6c6cf6cade379b2c10fc934432c7d40eed4b989127b7bb8bff78fcb1a6b325fa654ae46126b537f7f890ba5b849879332130b034f1c9eb0900adf49f5061e97d6fd72cf757ae265e600f2b52f5a6bf4239889b259100034025fdf59a6f96b0d3f4348cb8fb0b99ae84ac0300f2cac650705ba0457aa80e21007205c38f34ad8a5275402d3e9d1d9c3ef0330b4a6b2246bad1559ecca2b164f2c36d83fefbaf1d39d40221383093934d017f75e20ac92c2253e65e45d97d0924d75b883021c69401ad823bdc7f0fecccc8024b41261d72375a72146a6e77bdf4f64667fedcd9ce202097907740e3879b5937c9047a430026f9cd8c4667d4db2d1882a2dac4a8c050fa0527e223f9cd00197c2c6d49216b32c3fe0b53155d3490dc83f7164a9e6b1e36ac7f887b178d4e8167042debc319f6d8cdf00f58066a23fa3d01cda470c88da94857cba8ea0c5256601d181e4f22d0bd70f28350050d620d4f83405c7ecc01d56000e6b4ef7c1070af061e98196e6bdb64053229d65424d51b31e398da8a2f04e2c16cdfe35e8465ee380d5b99c5737a743356da3a8498b2438201bc7e885f1b519060d60bf8a74e9390aaf6b5e1cab12209dcc4ea18b126ce91ecd28096f822bbf6ddc4b0acb146c33a9c30062a5dd94bd7d04c40eaa2721e445c336c58ef85262a62469e977c1bb1fd1f208f0b1f75b8ac89c1bed3d583d7055d478ea44af9564688302164a8577ca8463a590c97d600e317809407b043056a5c7ecea2c981bc8e80d2d49887c3f230b36fb3ed2aee14616f29e199ca01d9db6b9220863612d0f543b0fe3aedc052da1d4a007e4c856fd89a41795cc8615e6e58f270d7f5b8384b936a43f3af94319889db249c78c972735df9e59ef0e4c314691f342b6af8bffae0b8d13058e781d787a35686c114745a261f55f13aaaa6f8cd2eac4ff0233516e404aa676a3ddce59bdb700b02e43e49e47bb4b3eb1fa87a9c1de804eb9dce0dea414cc92d5a48461a09a23cb4089748e35f2f2b61d47ee255c6d2cffcde3f269841838990b8776bd9773150f31235ad2aae928764ef01e6f3e94c24fcbce9395f7450cbb87e0888ae9ed8d928e5388819544cd1b6cf93a35220f81e1b9b9585f6e16059aa31ec13f1764a8623009885457f19ccba3a94bcd95bc1947aeaf1e037d3501f76bd054ee0613dcc3fbb31369e3dea284a46b973dee2919acdb85d9f704dc601de96962668844db3101795fee84bacb987c8ca782c8037c436aae680c3d285a5371aea6e4094f0e17ab6b45a2f94178649e52681b17d4f410e5a313f484c825ca52a2cc8b45889d8c08e9a9380cfdcce282a78a4ac02930e6283d77d719eb1d40f37434608d6c072170884ef13a8e7aa205908dca3ec441846c671269cb11eeb0ad0329a0a861a2263c90308ad59c7be29a9d3e8b4f0309d46285b6fb75064d417f25dbf7bcff3600e5b3262fd7077d08c9db4297027b361fecc88a6a2c80d15c9e81199c46a6fd8a6aeb4800bbb9d4e150c65944c1ab7ad31ff4acf28f528a5e60177534f501887837a41c3c705242752665af9760b04b2b849cad18108ea35292487be3f0b0ad9b90c081f46111f9bb651117cca2a7a74717ae79e00b3406b2235b887fe825d9e58f6dde2877815e0e4792274c7e27ba547ab43be8119d6da15215fa6237178ad5f05aa7c60d6651ee41663004336dea82cb124a4340166a9bac0943b13b4afc075ff1fa35dc17b9dc611d0486c685b11812f4d857413966783e8a10fc48318b196a99dee1745438a47cb914537f8e6961326291ced01ab17810303584607411048abe71fa297e1563d083b5531ee99a55850d01e0919ef58b134267af01f426489f733e36f612b4580770011f5829aff41f921432178742d1d7c727ae9739f2e3cd91ea79419e9d6469e6b5c8d5868e6d0da9313c3d2078c1faa716a94cc0145fd27cb7ef4016096da84178a78214913da1e8af89051f91f01f7503cc67e252e0320f6d2b0fe6a42b245f12a27130596c1cb3b216f0d59f9f85fc93e20a6c11db26907d566e47b3b6f2f481b0e913892afde8a4cde5a61f8f7190edea80cc86f2e0bea4f24879adf8cb125ffb5de67224909943aed7ce47e2edb5c724c0bcaa60f3e087911a54d251260038cca94b631bd21212ab7d4d48a8f4f8933953d54cb4a3658ce4620b5d752d48d0932f894d2af97e2664d4b6680c83a6585e3ba1b0bd315c0320e7584c6b460af08f204ec4187c26ea783736400e2b438bd9c083a68d25b19c721a7fdcff1bf1fa56cb40ceb2c2895f3b06c3bdb98f831d1edd83d8ac245a6b7680f30a3f0507142df9c149c946da2f02e6dda902ff512b8f6f0282facfae569ed9a61dd595145cfc4394e351a54ce428443749c2dafbf2aeae0275a74c167fb770e6c6e975bca4fac8241d94d9393b14bff8c8aab1729dddd94ee380e03949a8a9482fa4e1804c2959cdf3bd66d4ad75e49143235a50b43e3ef057d15dbf98293eefa88329133867d515670e605a5d1f864c9493a0c7387eb8b038a892908e8ca77e1a2f8b1cc165b4af23ca670f1c3984926fd6d415420884c0ed9287c8b6a8ae721043997038189c847731676a18f69df6e3048ee23d3d0bfdbb15ef708ff6dd648de4a4ec56066e3c948c4bd3c1a9fe2ee21a9a5ed0828dcb7edc4eecc4a634aa9272fe6bf8c0f0ca2d4136c13439854ab329c87de60cd2b538885ed074d3ab4c9e3a8ee64aae381fffa80939cc42f51f4c2fc7cd3f6b9335e80eecc53513e00fa979614c9901aa2a43aac3de4381ecf87ac175ca32220cbd72029742923b7a0ac579da860c6a2ed6365c678960aad19b77387486da650a8aca5906042c0ec72b088f74001659523afc5ac4fae79ab0e23f15af6239f69d3d1f241d2542831ca82df5ec324304700aa484b6957c4bb22f51b3d6a35949e04c32e49f93322574f8e538ff9f504f437682305b83bd81637351fb5cf6cbef33fc4787dd81363cfe43eb89a6863c9e33fe1a6a40c759d961c43e653828e2d7d670b735683d4464a79974510df87c3518a548a49456188d7f6f5a6482db266eefd66d471faae42e77bb9e8fe6ee2dc560dec5ebc2fc2bbd91d552b9b606a14d1c362e96e22b67754fb126a975617ba778469a001758aa06d736e1324875cbf23fe9a6c9d1bb63b3d1ab2dca08540afee3118b1fdc3e1f4ab1667087835095ee89005d0abc4c0e89c328b81978aec9c56f53aa1c9ae67e0c0529bd9c8a226b0969419aa913b884172f84a79c4f52c2daa0b9cca026a56827e5a200a5ab53929b910b7fe1b263c0a4af8aaa86315a78fdb84ed9e065a4cd73df47f66020d7d2f4511bab7b0a0e12e02081818889583a9c8ef9392d2e06b752247f0bd23b13bfbbfbed28cf56f6b6653b3b5ddddddddbda5945206640913076e07e367703e3a7e0df247727c74ccc0e45c3b629fc1c93518f39b8fdf5cfac8df71bef931be037df2d71b03c9ff01cf273f03f46324c993a68020af417e8c3cf90e1e35507e879c7c097f5cfb42718d9be95265ddf797077e3caa90836fd7dbfb4070b783147fc424c6bf23f1dbf302a59dc87fb0d8615129e331880bdeba0a45df3769639a74fd401a1c23c218a6be0a0d21c1d1840fce46f001c92408e295258454e474f80005b804084d050c00bde906b040f199080006a0d956f40cfd70042420e680da14893345000430f582ca80742f60a64a0911a92d7c61a8c05c4d60ebf62aab2b6bcead4e961ab5842c326a5396989b10595b50b84115216b869b9c2c2aaa8fac29554996949a42561375852c28d548161215842c13d42159445416b282a84eb27ea8266479a016e9e5b975f5c06e797a40a824f4bcd41e3d3aaa530f8e2aa4c7e6e6430f8daa42afcc0dacd75393f4c0a8477a5537227a59d40f7a35a8417a32a850bd176e717a53d4a5de93db0ebd25b79e0a6a54ef887a42cf88cad41be116440f845b0fbd0f6e787a4bb730ac226e3f600971fbc2f2e10607ab4b0d014bcecd082c372a132c35b723b0b46e3c60e5702b024bcc8d0ed695da048b8b8a040bcbcd0e160d2a11ac186e5e58bcdb1058526e40603d519fb098a83fb0a0d411b092dcee603da94fb08ca81e600d518160055197603961319d58d428578d72d528578d722d95fa020e5f62ee84e90a0347854175214aca491329264ba2b0b00214a8284aa454809242122428481d89f204e504134ae062e4491112880c19414a042141420001c8e7f4fd80b2254ad3074c3e3c80d243ea5b8a127604943c46144184141e30292d75a20c2184541051ee4001e287283e7cf500c50b4a9d2876baeaf04027ca1c28ea941c95677e18cc902f4fdf07268bc8cd9b9b74d5ddbabd02230506aa3f5dadefbdb736e94abb1767adf94f0c2d74af0c5ebaaa8410ea805037fa7e15eaeb0da15fcae41e223abb427c82686888e8cb903c9453887987fe7ddf0b2e3cf4dd2b2badfe7ddf0934a9edeb508771f5248a9fde01eabcba9a726a532bb57d158788ac7a59677bceefc5aa04ab217dfd50eafbacf78f7e1798d3fa4d4d7a5ffff77dd77a31a2dfa7a1745282a1ab8e0f2b197cc0830788a6611d7e9c765a1a5a37f17d67f865d61ae30f0460cfffe9efc356749cdb992f7733a373b657c415c0ba2b8473b97b03191b6ae0dcc5757b55a5a6eaccfd9a9698beaeb039a9ea4285a5af2b864e484b2a7d5557f8a86a41b5aa7a523353c5821155496c3154993044d510af2a1048a8627a7244ad0bcc36e587da115db5353bc0e1ba426393226333418c2d042b22b4d8a8d4c0a54a8d2b057976a48460c24685344f5924f81064aa4b936d862e3d3e3022ce1029fcc0831d9b07736a38bc51b3c646c31931646a75b06a74be78e9b2844acb9521556a7081498a90a82b2cd8a252b03579c253644b845a1e2e40de70619221ac1686081b93206c55be6a77ead47ad8c126a50d98ad1a96199b08639e70a8056165b3b265851b7298c176844acdcd546d8cd40b4d6c803282e48b0959889410a4c8961f44b6786095c70605ac0710b52f5e41e86c8113858dcd05343794a9c9e95d81b1f1aa6c4e59be6aa8fd20c39c177e4ca915f1c406c2922515ca1c0162a4d66604164048a2e5031b0a5a96a08aa8a11102890f2a74d9bac869c14d4fcd085ab51d72b05589b135a5df550d2cfdb880a1b2c150430d082b3a50b129e1d5c2a4d4c83cb18561d2d7211b16a8beaea89dc992a4afe857d4bbab2c4fd6f675c52dcbbd274e10adb82a300837978b60e8aac04e1808e885ae0adcd4a36332e99aa6f7997a749c42e7fcf77e3f30beef5e6b4d505db9e3f87510ecfaef8b4feea3241949dd28bbb8f936868ae92a8bd31596a5ab1bc0c6fff86a341ae5bfa335a1bf6fbfb05d0fddb2727c37311f4863808aa923dcf07a3ce4e1ddd4a9303463785ebcd9940f3c0314602a0bac2b8c185e199e0072e011c94a2ca00117a3a0e265db541aded009024cedc0c5ab32a7747709cf01e0042008135a502178727ac0905abc9f2935bc014c11b18567c52331f58607e4f3c31c5e6c8acb1ade19de3905803a2b8ae0955316073bbc74ca01393abca92c3af0b07812044d91e17d294636fb8b191e02a6ee24e0ca0b0f3765440fbc2178bc0d2f8e50d885f7420440d414f0c563a1a7ec9679cb8197899c9411d25435268d9910b439d2c312205173b4e06c11c0923a52588c847dcda0a386a8b3860e142c60de482900881e6060889aca43555aa952e7cd11302cf4dc401100969a312eb450b555049a0e44283285841df2a060a48dd4d03d626b853136a0704311031001c90f545f968c34e182a2d573e55545480f45586972801b1f5662a4d47879b139b9aeb4a0a4eb0957940eb43082093c10ac9cb6ac8132f3e04ecf8c0b5080900153a30720314021e2a6cb0a6aa4ec500076660c90187ac010d5c44e9415108e703511b215a5003eb27a3cbd1056e40a18345e5574a093070b139d345174008285af3b75ae906c893164062c36f47085881f684e00b18282c48ac7b352c1c407192655a488b87246ca47140cf403121de884418186293944544822a407355161b4723ace1d2e2400b9a28208787ca081ce913a2a28093323958208104e7a884188189a5410c40976cc9ce0464b4a072864ec97253ce850456baa6ac90a2d5849e3c5094c491929594b78601881c9852f5b96d470c4922b3b50e1f083ccd60f4db0e481e28510420ad6073439e0c005e585a92b39488153c583ae22bd14747893e64915272e9c50864dd418077c10840e68a8384959c2434148561a2f3d9484d992f5e585281eca5cb13365cd931235338089434310614c7a52bc30d1ea52b3248e0949d69c241c61c7480864e654c1b2793a50a285054cc9112f5b4a4e965098c1ea0dd794225a8c1701402092278a481039cc70a705a82c72c0e02084c621ca08868942c4460dd8971c8a28c1cd9007c0610106b03426e0f1008c942e24e2cd0fac2a55a89489c09c1ff0605123e50bea0b940060296307cc0829a0b00391353df2ac19c2f5c1559c1c429090c046082e568af41e52500188191bb66cb09292815949e8d869a342172457593e6069c1862e57b4682f7688de123c64de9460c74e0c265f3fca94f015430b555c80e012a6082e40b478d98011265c2569f38399315da210808d1b168084496181091c2f2370ae5a2843457391fa810327707549e264c9090f51941f715cf8a144090d7a8b552a9242579b356ade9c61327205e6cb1031c84082168bc74c9a37594e681ac23b9a92214c1c189c7aacc9a2035a236531597fd6fa06110eda5269241d46e388983c5b8ec652bee29ebe8ca67eb4a7716c7bf80f16bbd9cffe078b5dcff7f0d5a65d769eb6cf8e6e3f517bf90a7e696e71dca21e4a71a653863820e94aeb18083fce850f72fc1fef80d98d6effb67efc6efca6df8d3cc54c4edff43bd10cf9e2eb5ec6308a3b923f92f7bb5bece17a63cd6f31fe3eae7b4a1b2382fc84f476d096f9e290832eb4be6216310a9df96941f822b721b7596771289261d2941ae7a79f949434a506fae7274da9117bf49378924579d2508cbff9e7c71efdf57bcacdff18ffc162873eca3f9bb5e648baab38b785702e3d5b883abce1ffc0005f3ff87193e7d0dd28734be2dc85e6c9195ec71d837bc50d70fadd0f0d1fe24f054a3e46183f691c4fac626ffa1d2dc8499acf0aec6ee439c41cb8676ea4bfdbf385e6168729ad289690de2eea0b7efe2fca62f8f7c534835cc439179a6b4c44ce196bbda6b405488b30c637e71b4438138888b4be7aaedb2b2d2b308775e099a52e7e89f25547475fef55b4c0ee6c3ef9aaa39f69fa3ad5e9eb948b14b8bb8f17e1f163eb394344f7f843b068c733501d3f1a7badd147b9f9414ed28f8b4ffb559d815fc7d016283f79a9df76d32c571de19eeda66ffaddf73db399ecfba6dfdd3597e22eadc0284a0c2e294331fc225c861c9be78973e779c4037df38b70fa6fb59c3afa28c94d9c8fb4f6b61f3ff6e73ffa2969db3bc0af7d794d8c6e0db49eba7ead27bde7512d2b9df6250cfcb22f39381f7d7ccea0a1e8cf1ee5f64a707ecf799e5cfff9b2bdde7e82f7e4a205b6b75947ca9edcd7c689b22eecd91a5800e7c29c96665aa6666ae29c529387af810d6e6014e387e78b9d7cad7b6ddb9d79923a2c9598aa9f1b5a896c6f7eb98f747474e541eea3d085f9f96c619e1c29db3651d6858fc7f5ea5869fe4992277992277992277992e7af409d24637f6edbcd11fd30766edbd1532549beaae207ad90def85994cbb13cc3176b5fb3d96ce1283e49a3f64518ddab521f457d9e5fabbdcdbaac3b7f9c00e7c2f07141d2d586a9117e046adbeea094b22fad19ab1e41b757669274adfefd9d632dcec5be4cb7f9b17d3e58cfb40fcd74b693665ffb529b69896e1c09298a72209c1b937abef6f6b480f6b20a6828c72fb3a329db381fa9f8b43c8e2307b2007cda97189be07901f8b32fd3d34629336b3fdbb4976173b6edee667c5aa0b4abf11d3d96066db6697ff55901f0692f9e1728ed687c47cf8b34f0693b29fcd9eb9e6d77b36d77e75e47dd491a92a5a24aeff797e7b87fcd686eafc518fdbe08db9d151ab9d18749beef0b41b0ab8f0f6ee57c5600fdd8dbd302a55dcc883e3ef8f7a481be3e2f403f86c6ae8b925461fef9e7f9fadca291f47c71da883dfaf9b44069879e2e2c1763af4f1ab1bfe705b1476362d6918fc91be65e91341e778abfeffb46307cb1d7d0f19361ba7eb37c51fc3ec849fa83c5ee7c11fc52243f1c4b71ebe8e1db0e822198c6194133490f5f07295dbff9f69ce1627ccc739093f4078b5dec631c7c7cce389fcc3f928f4ddbcb202429c9571da2288ea118f22fcd4ca20e41ba01babd2ab3f581e2870b215dd5eff35ed3ef81f48fff81e1af48fafb212b8a6f3b38829fcf16a0ee638cf1f15ba12ffb28add038967f395943071f95d2f5932f93d9ab02fdd9cff88aa4fbec35cea55fced2c71949679fa63fe322eec1b274af37cc4ebe59264c3f3ff39e295f39e8e9e3bda6f64746c3b9efc32fc228b739dd48baa36f4f18e7932b12299e3bdd26ce8d5feef506ee478ac7f1c991c4b9871392ae4239a11b668fe004d10a8cc2e292a4f6aa4c1213a3785da4cb88ae3ffc7b5e60b32e0cd9802f1e656bc374fd2007e9df0f5a2105c32d925b6052c8678c1f727b255891740fff72f2d71be51a7e109234e4fd0bf7c79172d7a999122791ff5d9191d24f18f8496e716e44c24619f794ae0fea6f7c1d6eac04923feef5afb6e35bf1575a1f7f7c2bfe0ad44730c47f1476bc6fb796011709c78091c50f42928a7c55fa7a3ef387b5fe2af1e3b880fcf2296077e56f11041f04c1b7fb47e3d2fc5247c76f3e07dc0293dbd77cdda06323f1c7c7df83233fba5be4eb87d371e4ab18b4427af2bd7e7807ad90fe60b12bbf7c26a7ffc162677ed3ef4c6eb3eefb711f89e5db8cc5ff46f1c1b27c8bd34fbcb8b41d9f3374bf3f628cf57f18638c31bef7e2abaf18441dc28e58fff84579dc2b929841bde291afe2dfef45b08e743bfe62587588d476b15b9c08e96aa926d07f1b90c11fc334d7f5afb99e3ff249feedf1412ceea3dbf1ed8f03e1d316fcd212f91f0d520cc3ffee23e51e8aff33eafd337edfff68fea3bfd4a1f593fc67fcf2c9d77ce5c08292dbac5b37e8d9087c7d3fd4fce823d71b161616c622eceb3b02d7edd518aa9e83959e767b958315ce3ba6fbf6bc407c2612e3cf20f619e3f1ed99c1c96dde91c6b82def7fbe23b99fc778ef1dff7e8df1ef782fdf51840edd7568ecd1a03a7ffc1d7cc7f9e3af250a8eff039a1f36c7babdea926387491e144afb56c8f6aeba7690911ae5bfb65b6bdfac80061be414e4dba11e853e3e6358acbe5aac9e9fc6e3a8a4f639c829c89d1dc56e4b1bf22c56b73c08496a7bdd764c832ccf72935bcd3afbf94e89ab928ea5155a3354cfd80a16452b39455eca1c5bc1392fa9bd2cef666a5291f2528c8cd5ed83c52e3fa943704e3e9f3f2fe5144eff400fc4ef57d6e5f711c52f95ab5a57c8f9ba9f5fb620656feef5ae3afa88fbb5e767681f773765f6b32ff1c61c478d12eff33504f0ea5b9f27218012dc26ddde8fb65ff4698fd6d0af3dfab54efefbf0db6daf3dbfde2f1e18ab7b8ae6386aa84fe26b9478750a8e1a40af7e09129cef507992fa403e1c6fd5e8884558d811fd0706edcba77df9eab63bdb3ebafdf21f18e5d7befc1a977dfa3418e5e5397237eae1b95e8431584ec1f5a5c11c0eb22ebfca93a6d450dfe7b30f4fc25afc57cb05805fed97d02e007f5fdd3e7f7f9476b8c7e74f053eafbe0d1ffe195c28a0fd76872bcaeade7179387ea9b22ebffa3eeba58ac1e37e6935f8ba52bf278c2f839f57b9cdba19c76da59d5dd127f9fd229ccf97a53c47ee6baec7360cb09f1f80fc42fb02605fbeef9caccb5b475f73e4bea6abd1e5481929dfaf129f1f3ff964ec9453007d7e3c24a720f14f4fe82e817612d037f960b1c3fd1ddf6727f97c132e49fd261cdf817bbbfb6151c209f7db09f79b036d27dcff8c24f68f0bdd49ee84e3533e6e24bea5eaeadbddede514582bebf28be2b98dc0c75ffa60b1fb79a0b7dc45eee5fb3c89279d7ef88ea0b73bdc977813c44017af37fdffa844365bdb057655874bcf8fe1d8abaead9e1f6f6114b77f4eb103e87d9ec4ab4fd4877a89b79ff3e169d6e57f9f07fa3525b17e51d6e527f13ebf0e39b159a7721a599771bf8307ee4bf025dcabaf3e099e661d900fee7ca1fbc8d77cc4a28bffdb078b5dd07f7b552ac19b821ee84d8e0377fc4d41eff3ebf838f3a6a0c73dc9d791e700bbc8a382780724fe13532d9e03ec3e1c278a40696ab3686e14e563f4bda5eaf77411d581cd3a1cd501eebf4b55ea6da9bacfeec066ddee0087e3e252c7f134eba8ba3d5bd0507fab5cc531a51ac6bd50de17df53bcf849e9d8ecf6aa0b4ed77f5bd0d40b56d6b12266f03f0da2d05a058edb2f88843b8ec142e7ac84882672fbfd4ff355a95b13f6ef5b1b43ff67f4696b023bf120a503096114113cdee1d73574dc7114adb5d1f7a2cce29cd5bf9a544d5d3d6f4bf543abdff33f141fceaddf9f31ce1821e85fd746dfe7fc5f9d27fd7ba50e437f270cbdb4a43ff09471c3bae6405702fce089e3a5b8af40a36540f878e7351db2839b8293f2e528d13da084afcd542db4cda7d5fddbb6176a11c4314973b7577448e8eb09dc9214777b4567483f61a9b47867a023029d11e2d071ea6b2e6745b9e9b53993f6f48b5ad45f1884f99d201a1a22a202f58b5272ac40396250b168a80ea98b2690039575fb3b5b7ca228ae39b3998a62dacbba222b56a9180213144db0c352d6d916df1b2be89ba9d671acbe4f8bb8bf79a2e1cc81f385754510c0b9ab759ca5381f9c0f8109bef2bf92c301fbe0e4d138285a7c03e5268e1baebefe88635a4a2b67ab23e130f66a8e11ad35be5d09df0f5cf0ce10c5e29e582070501e0cdb24e162d2c60c06a26dfd34208c42b711330271dc0a5877af4fb7576da8b491d257f17fe0a0c03c99982b88c9c52462305ef84b13b9a3c1b228a6bc53163dc0a638812db66cc2acd101d7a4b556f58b311411727980d3a2ac35913b1a2c8b5a7be12f4d53d3546b38611d22adadd66cb6ba7e20366dba7e218b73195704b459edf66a4d958e767bb5e649175728575b2d7c9a6cd6a34bf344d7d80f8c1f18377db0eb9e7b69b3d96eb67d7b6ddfa0edb5676b60b63720db1ba4bb02b19d01ba2d70ee0b985b839d812b81fd721cc71b4916659dfd300c89b2ce8ae250d6d9ef0341f0cb9cf34debf45ef176bbddb8b561b1ca5ead79eaabd84d7ca586c9d5d61d7c6f683130bfcce0935f6ab3dc6b522ff389c1f96479bb3e5ddc2e6260fef9f9243f7c33cbc898eb07397ebbea40c23ab43cf532863b923a91575b70aeb6ac5c6d3db97d59fbf91fdbfb3c0ee871e5a74ffbd8c633f44dd9ec7bc07af94073aed26891e9f96968c2f4fc2669da1386ec315fc97346f9e9ede6a71fdb16a328cf0c661bbf39db698f78ca98718b73a9c9ed176554b6d7d9dbb73d4a9ae9364b33469e32685f92a78c98b97fcc2ff9fa834169720a90f8a0b73dc924717eb94f128f92d836ebd00f4f19b2d983a78c9ed96ca77b9d3d68fe784e70bbedcbbd964fe3414dd292e3b7bdc98d6c8f7223a08d6d3120a01f1f1c0ee35aed6b58add5ca5d8b99b52ff1a71f536bb52fb1592b1f97b572df628f315f4b5c96e9e38ea4e2ffb8309f8ca5b1bf1f3b63dbc87c1293e46c66710ef719be4f5ea5b1eae49b694fda83d738755b8ea66dd5d1d397bdc528740f5a7b8b51a01f8bedf357d3269e2e6ea7d5b61112ee34be9ee5e9c23c29f0e912e319b8e31fcf182bdee897eb4d77cf93d8413fdbabeeb3bf2706243e88dbac93fd78ca483ff6e229a3875b9c9b7dec8bf26caf48d805c618d8ac5b654f227dd9cbc0dce29c8c946b9cc339f34f93936917b70f961109df5b6b227cfc62cfe373463e5ba48ff7f9e6837ff13983fc92660ad14e174a3df6e1ffa42f7bdb67dfb357f02706f9b3277fc67fd2ff89217bf4658f3e4a3ef837cac9da03d0643bf6e976a1d4635c688b393532b98a739ba8e42b804ccb807b7ba4f6cb9721dd03e57852a084e5c5d9023f0cad7beef684912ef51ef67b1eddb02c8ae023815d7c1ce223813334e8e151892316fd5a1360c8ad8d40eb7f6be27e3e516be266137786453b3a7cf39230c20767840ffee581af7951ee17141fbf3602677ce0e34b6545d222922641fc46e00cb387cf570f2875f07511c6f906fcd445b32cfff6300cc317e2ebd7e39ca7de622cc70db43feb535c71d75a6badb5d63a86f823bf6eee56c5bd76d045b083f7c70dd4b37360598ee3baa38b1f6ecd57fde24cfb288a604a7778f02e57f85ae4eb38ea7e8e7bbdb7dffb970a46717e39fe7abbbe467e715ed2d534531a46f1d1c0f3c5bda63de3241de37807f7f07e197e79bb4623f1f5c9d7f5a2b15fd31f6573fafd31c647beca3812ee291fc5f38ee38ba828fe0d477dc5f02fbf3c9c0b67b37b67f7c6eedf18e4cf66b10f6f50931449f72bfbd0e21cfab4ddb3cbd90edfdce3bdb31dfe757355fc7aa9f410932ffef86b587e696e4b6efb75d906c3f4d8feacb47a20087bba6fafc736d8cd6dc3cc21f7da41273b284dd9fe7abaaf558f6dddd1bd5e2e62f8e139c3ec3704fdc717730a19e25f2ee2dcd59924fff2cabf6eca17cf16e59d438e22bf6eae04a1a5aa558175b393afd573dd7ea9e2f5136ff60fc5fdf20341537f7d6f8772b9ff2c8abf8e9fc991f04a120de572ff24d109eda5bd6c113e5205d587bafdd2c54e2f2d01d20faaebfe85cb562f6333db2f0882a0be3374e28599df6a2ffd8551880fd57779eefb44b2bb8573264d24b5d75551fb7cfbd5468ba093429d244992ec799224cd1b47c2bd871b81ab8df79024499224698a2b8df770a2fec50b555740b75fbc3875aabe2f997d7bf7e926c9201886faabafeb8e9e1fdc245ff50bf3c130839fbf2525237880f54ceee8e093fc8eb98fc230237ab8dedef51d8ce2fcfb57fc55df01ebf76fbed7bc77450aa27bc1cbaf569774154533fdd2efc1f403f79af68c93c23034a2aff962a5db7555dc17ef60527f61d9afe987b1af9e3f0221205a17c5d7380551d6891c9471f0cd17ff726d6991e985b1e2b2ae203ad145f03378d71847c2bae76be6d75d5702f0efcbf6fab16d662e1211e5fa4a548eb30db2eefbd9a37c7d15f30f638c31c63394af33735c7537cd73f6e56c835f6e71fc5235a797ddfcafb183690c3dcdefeb7380542aa315ccd5c14f63a85507ffa4eae09b235f4130dbf7cb0efaf8604ef24073bab2911c24331971a7830d728afb947bddc11b27baf92ba6d241248c976698ddc44c5807be940ebe8caf39261dfc94af9fa4831fe3ab6aa483413a68f235045d7b795d152051bf1cbc71a293e309921b8d1fbef832c2bf7cbd5b93e35feb7555e82f9cd35e570290f7dc017495b9bc9802fbe68af95e516b916b1c3847890e660ac69e2928f336fa5efc1234571efd87828ebb086ea3eff116af8b7cd5ff7dd092f4fbf5762de9bae617cdccd5822ef5af12f85e4bbae62741cd71e0aeff929fe3f055bfb8fdebab86917690af4a13c826b02bf86363dc917f3b7eb3236d8472b41533c2070a5102767bda680013961df9d9867623256c479ee49f36f2569d22613bf24f1b01809af252ded99206cbecf6b441424813234a4ebc8409882f58d06e4f1bb6352ef898137b33c5839323616169b7a70d599b354e8ef09134e5cb5bd6c1fd6d1c31743fba61fd07fcef7f5c7cfde8fe0f0d1f77471e55503ef9383ca0d4c52fc7d8a331be44ee50121d77d4f94519358a2acf92fc18fadf3e1fdc472d40174651b1b7fd468e1e55f2f4c5f34b72177b922f91e0be38beaf20dd31748e1e45727489dc9dbc02285f2277e9977c89dc958ffbb7631ca0af51e7df504ad2a3c813fd74c73edb48635ff2250b67873e8cf0f39a7b94138b1df9d63280822d63ac09db57ac82822d3d2b81edf73f50dac9becc26ba774c41cbb2a4617e119e117e36fa48bef4edc828f28b72de4add9e2f8ca2c8b7fd46087ad4d287cf19b66e87b28e2aa928ebf00f75d2dadedbddf96b1a93f129df7f9ebee6e8e937ec1727583d7ca3fc619ac4c1d9ebe0aff6abbcf2b2eaa0518cdcf3e798f51b7a4e96fe89fb8886f9dfa37b87f9e8979fa9cf16347c7ca0b46b9a02ea73068bfbb5ef90acc35f3edabbbd928b8aec715da821bf5ea8a40f947653be27839ed2cc3d60a9fa910d33cad1d36f9488e18c0bb1184fc24b5987ed0ee5f5152f31e11df9766794f99a1fe4462f947afe1cb47e83022c7cd0f3db2f479ef4150fe938f39ffc1f08d3d70f8031b78fbb2b9fdc4d7857f29ffce53389e57f1b070cdcf34fc0eecaa35858cf5f6a1318d81df9455847399dd8915f6e1f61585818c96dc05022a66995f7571ef7c77efff295877dd3f38d2181992c613de97bfddd5072e1ab67de81c749f7963c40a07392fda2c5a92359381ce8cca392ecf388f59edc601ff35547bf8ff7b5dff3b292cd680077a506f0c409acbb9f71349cf082b2f3e49be2c51355dc0c6a0ee70297aba8044cf6863dcc32e84624000000b315000028140a09452281400e4451c6980f14800a8daa4c5038108b04318c6218006118000400000100000000008000432c6b94e6070e5d28c4c6cb943aebe3db164fae30974c25ab9555ffbb3ded281adde01e95b54b8dd8c6dcb133a3ed9916f57c0e6f43b6dd716e9d5311cdfaf9f4b7cedfb1ad996065dcc77148063532aeee22e2857b917c9f7d09268060011ded780cc73cbfe097dfb9e0d6fb025cdd2964b6ee468240e322d53f61f459547c97119eb4b7879f44b0c48896895c30303f9dc4de4c34ecf316efaf3a7e9b2abc5033a6b9910678b43fd6e84ca79c3fc149d3d275ba9d9340856eb065e4b68d6f8c64ea227b1939ecc3a52d2e06c4a2e4c3f36c727cee8b52553cec21fe7de3eae561da61495fecb7623082c6de8d3f8728b9160167ab3889c42b1f609b159d9571fa6cd03df63b163bc61f1ecc657c91d8767bbc5d5a7f7842f59be72e12d7c3b206b360d5dab90d7e957cb836cb20497667f76ad72844f12c9ac57bb747708413431aab1914348e9f0e877c687268f5aef9b83e1f63c240c7313e1679855c11be9cba66566ca44ca56bfcee516f482778aad2d755f13218284e8e0ab18108aa912e6f67d4ee920767c02f4eb3e6cf8ea3e36fecc161b8396a6cea588a5b60a77fcd5b11b94410538c1aeaa1e4a223ab5c490dfd7ecf3d9b99fed06884f9751550f32a8168106ad7e23b342250a7f55a6ddde105338e607505542fa3fd7b7a996fa5679e79cbe2c3f2d2ccfbce5ec7e90a835943b6930229185a1aa40a1ba79505ff20490ad497f05f49612004e3506afd56c92ff4b03eafe7e6975110834b82f77b3088f4a9550b5008215088c8bf806495402eb542cce77b8c4283c3ecca16dc4aef50a61c3661fc1ebcd300bcabf5372bb149fed705469e6aeafc3a59fb9fddd74b76aaacc54d870935143808731e8476b6bf69a81cb0f08f184517df532b861d40580fc83f7700b03716303880f0139d03593775bbb5cca10a663d2686c1a10b579e988dc9a491d2ebc3f34a6e6466f0a884608faf543850a1c4006d476830525cd52ae37efd125663fc1af316e66b43873f1a25d3c3a064df6c06e7a34b9fc7413a3530c06dc16ab8a4ff0b7c8264692d4b14b1c5037d5bb3bf9c8fa31c5e705abb4f83eda370d554186999f0c16e4bbfbe95007d616e8c1e242cb1f04d80e67d562ccce5707d788c2f853526ef56f9122df8e280d6f5f2764031d8603dfb6c1e9dd46b3d96a07f495927aa5c2b250c17e06aa90e02c0aec220cb25d42ac6ce32ff55a74b231b980c96f972a39c2f8a2fe64dea0a997545f7257450745aec538bb9cc5e6c25efb0a1c08a6a98785191fba002d74f2dd9aa589975e875414caa824130430c0f761ed56bd6b2788eda5b49117af6b11a821693a6226dd657e1c18a935701645bd40de81599db3da9e383b3fd460d5510ed0b2481f7b13a68e741f07bed604eb02b2da69c1d0f78413f880af586a8ced3119e4515f39b082a298069d572ae7bac81194a86132e7555d15848dd88d3431f1766609470ddf4ff224787c56854088b6a669c9dff645a1013f99947a8b798e50180387e467520d8bf38e488e5d31a0af184111dc062ff8150399e2779344f5c8b8622ffeb00e6156b1e86e58a5799a28c25ce7a8ddb514e4716fb073c9ab572618024294705eed9720a7ee300107b1831919243267bc4815be0a85b21980e1c9fd8fad9c220e178e3239b287ab3f0e88a33876f020b16d10e663d57cd770138664351f81137cd64efa5de3305d8d6dc0289854420b620bfae8d2a9f30d6bfdb3043dba31a3570ac275e7794b5c68300774c90ecad60b296a49be9d86039299810f01be17e5fbc1cd3f066eb7acac4c9e7179f26af523f60e3aea056df65083c13acaf7e2bc0a4631a19d949f0a165ac25e28ec51fbb2a5f0b04ace14f88c38b7808c4df04cb37e359be0bb19422b0abf4d5b965dfa56908fe85199feabeb1392ef198b11d0e5dd51ba13f91b053c6ccf09523e6839abcb3f52c8b5cab17d46536eb3aaa3ac818a9a52fa3bc4f5eeec05c127c37dd09264cded6f8e6e5c8a33d72c8e060eae0eb577b7febea32a895be52797d68b62ef725479936685086783742a06844ddc992bd21476e2f06975ac445c2d3674913559b6b21c3ca21beaf5bacedb9178ed9a15f24a65f45fdb64c91e0fac00c984ededa2c6003970e14e716e04a22c5cdc6a5466901c22484e4fe097834287abe2ea65284c3633862c11482ab64f2c639b0dab9a18d8b987423c3be28b10535842ae2f55d7bc9f4d2a3c4e9b45c84a02c0e15540b2dc37ab268ff4370b5b3b1efcc0b6743b531b6987816e4dd27ac5141bdb03b3451ebd6262646cd0be099dc284c9382959dfca6297d217ca65e66bb50dde583ecb8d7b9d06672489b7c11a34e46c66f76c9a3e3802b850f745f223dd134dfa194d3b58f6eb0a7acd122ae71736e83d4d190ff300cb89feac85412147b9c0b6bf29a274c4528fc64d801a2d4cb6edd8ff637c35dcaf3927bff324db4a10789a7435f81d0062f959950418afeea89575fd532984a7191601e31af5c6d5c50c103a116e92db31663b98737a83b63151e709df2f4b1e8bbcdb20e8352d8fc4774db3dbc89de6e1d4c7a7d6990e7ea2aeb905a947b64f8fe3ee1c0dcc3bb183bddc33f53a21b5a0f276ef108bf282ab2e8cb0772018c57e02730563bcb6af3ac3617ad9a341fa99100332be1e2a4c92648ebc690b5b5149286101a7f2bb18adaa0beb18ad8906f8022bc0395bb05812912738b82a987f7c2ce44b0b3315c8ae765a2e19a3c01592043922aa869fbd3fe9b7880c43904e8898ed9f025614efef0227705900daa48a2e2c18aa1052f3d76dbfb1b4f4131493753b1eb0f4b5a486b7f8b4a80fc0d1b40e7fba4b1741fe57e0d41c602acc12e2a91ac2467bc2e74a1d6c509e2a02054f3bb34aceaa9903433059959a05929aba3991d7ea5cf511f52ebaa8b6c7f20b6e02c27101fa3e2a685ba24872f2f59eb67fa04e1c48e2b3e5a8ee467297e881f39c6918ba51abb633029dfed313c11fc6f7cf40cde2348247b3afe83c33291ab295b51a6882660baf8bf7560ecc5df845e3d057b09649a9ae681c27f765c14cbce3f067fdee38823852138968f0c6d55c51436b4c4666344a69c7934f98917bb73bb7272c3c9d2dbcee1e284afb092989788a2372063a7b9adf9287771ca5d79508682f59dde7832ea5447f8781d85f236e4e24577b269310c939fef3bd5614eb41a5b184fada09923def2ee454fdfd0489be60597f07490b9952c5a780d5f27639b04c63dc75b6681e71042a9734b34add2037a98adf5d5ce275b16d6a0884ea603a5baa22a0a25d816565a94c628bb6e208a984f0b4891924728ca89d272ac0194b2d209175352635feff8d07408818ab000cd298a99251eb9ce1d057cc30543728c5b5399b9cfd5892bb81d5e5d8023af308c16f8b5361ce3b63b6d6c2f32950a38219f9ec19d3482693c75ea2ad7573c611e85b0f7ed500a6b6ed71b05090976dabfc7e6c849b7127dabcbe4a08ec205eb43972bc1b8931a0b91a34f73a95f54e00809e0288c7a82f7e23d918e07f1a737743fdaa4e52f77be816000980ee6e2421d57225742de8263b4e6ad2119abdb3006c4b848118589d30448cd60b04eca614961160a8e081e3534148a4245e9576189d13b67ff7f3c08f2096881cecd3cd41790eb987259c106ae9b23844798eaf7babc6d0bce9704eaf4867fb665ae6e244d4b6be6d112b154438e6b9836db59f91aa7e9be92c266346daa07e2e876d7831483cc8e19edac4657fbee5ef0e822fc111b59d609c863ba80136a29cebf9e35a54485a4088cfe1b2f486fe62b1789f8615ca6212e59e19e718e1e3212e9a00c28395fb9a6cdaf350e34d32ed221e4364debe4ff3edfdf448411c312743928ca5bd0bed63728de1b4e07c961c086cae09c143ed8542f27919dd2482eeadfd13eff766baecfdd6339780299b565f0ebe2f4d0cd634532359cc45d5f91ef6878481d33572ee52b002cccbb7669237365e836211948505fd35d3749569383ba366fff911dc1177a51bf2813e3b951ab94d033fff0bae19002d0865c6b5ebae5a6eb32721f3abc1af58967f10ab208cea663316193873b1dbd2d6b8d6a195cca771422b4694886d6718c2ccdf2cc719b57ef51882315114b752aa4a9f3c627542a3ddac49c813bb1c72c3f38cdd57c6d981ae4ad564235959641440e82125ee7c8d3184e70fdf008465a890c369812be8a3cf692f51cf725c391545b2c494291956fd122294b66944ee1b67ebf9ff484dbe0d86cc52a07669876ee991bc0a1eeea097e3d1aa3babf01526011bda0f5d51fc15f919fd485b877098cadd89b31239cf3463521586a7d7a9d923154f10fd2dee83f816da9a71c1ce7eb094c2b05b78ae61b4686a1d7285ff375e3b4f19000ca8f72bfe612841b3f83dd8f880b60fd122e0464872cf6318b07bb0d6ebb1be8c6b4adfb8dd9addb0d28a976dd260cf699fdcf8ff32f9bd88df4c4461ad5bf053f503c58239cf0a61481e842d0e19320b3c98ed811a21607048305cc658fe3f8847bc15bd80139ddca368acf78865ce5b5b3cf55a4f5839ecca5e50c95c8f6d7f536d46de047013027dd4d3002f41bd48e3f004749bd657c1fb3385d12cc18991515a05a66fa4497f863b28d1f9f9522143648881743855537fada4c69b557313fab29dda30f0112d738b3e84ff4593d1be6282e25b36a4b34eb552e1566ddaecea0f7621ed4636dac4a88b51408b8bf2973789b4f828e5633cb3cb0c94dbb3239f0d0b7f21e1da0dae62e0a29e7a3607202e756e255e4878b1ce325a10bcd55f5d74dcb2c3a5c1332dd0e12e7593c65efb780c36bb0e3dfc4d1c54f1b941ae440265fc787c7d906dafbba44a2c8b779054a09968378225006b7b939a3b198ed8abaafcfae3ba78a1c60f900460546b0a0f511fba95af32cb04b10d2006ade64f36295c1b34f8a76b2247088880184c3136d989e3c31784bf409cfb35f6fa0c7f10af0e8268e4850a36c1411ab3cad48bdf218c4e2e0fc53e9012b540a378caa1dcff58fa6e01fa752da69fe2b4e484462a5921cf7c1b803ee477eabf4a90393319a312c7e6cf591ade01788acfcc1d0d7a31d29bebbeb18958290a596b44f273133be85ce3c9d6a1c9f8c83203811af0024c080222fbb369104a08cf4a63d3f8db8977faeda7b3e2b96ea1779c8bd273b120e99fb2fea6cd4821abf2703a521c84aef86e1d8b3ace0ce69516cf243b1182e8a75ee2240205c7330f4da6d55c9c9db21fc0640084219db0deeced056acb117a50a3855a7895c186f2d56ecad5cba6d80bb3efd3f2264ff7e3178561adc61000f9ed4c136b1389157cebaa655f6b7fd804c10ad58d39b62a33da9efe234803f8743aee36e47c17ba07035605876a93eff3bcc5572de6ee3a53a3abb72f178dfd509363a621ef5a01068bd0ec8a12ea288a8510e64b1335c635b8ffede9342994dc6c07981239745b78e1d6c3515ac53e6efc17db5f6b91237a969a5596f98fdc2b11019080bb2dd11202b8b6e4927dcae188180748ea9d009562069fd3fc02d24ed7e494688a969867037d0f18d78b7f9662cbd696dbd719fdda82bd4ef86ab1260bd58880142812cf666bde175224ee6eee3b87e0eec5f068ecec2b5546c04a83779a9f4202d7256cc1fc0ddd3de4cd3fe20ebb7b7b68914c91f07d3dfaa5897bebbdff27ed5673270a905f06f48b57c87197adc9850c504731a2e3a5f78a3c3838061b54bfcb22151b40f44492f6e3c28905a449299222ad216f51b844b0aaa3db9667011f1bac2cc1d05046c74eecb09d7c51fd5478d9dd90822e7d5103d71064158a956319764bb15f1ea3899575815db9ece5d7a9f963cc7e445f5b5794518c37e4a3578286974232c252d11dc828d29ab888f9f340c935136f5b463fbf1f81b75fcdedc2f68b537f41c16706ba52f48e0144bedcd0e47baf1fc5bd7d73760b235b059d1df52f8a803a324ba61ef5eb32bcf7d48e15a50d26d6cdda86c937bbf86e0a7d19ed2a46fac1cd8d3a23d26c4ab2082019555d3044d96c770cb88e6a000a32ec109e8c751ed1db306e721488f57e4c000c280fce0ad061d7d536973550a9a9b2d573f9da5ccd1dae03592ec3c83a48de54a672e4a89eb6226c1035ebc5ded239af15c04f4aeb78c218ca5729f635f8c43a5432f7bac9d16a8cdbf6d06c73eadf2e6a28495e513dbe86188cda1cccfe87e70e4473d47d0d1113b60e5f0638d80f86c74ba8b0bd8440f70d58f9563db62ea006e4b48c94c0443d9b21244e9298754bf6714555dc4d3272a1326a920d9285a0f1aa1ebc3ed23404f0e5fe50ad6edb34bece3c832e3d2792b89eaebfcc8e112ec20d1ac8abe122df3aff1d36a2dfd43a55c41e459886fb83b260a1148e684031168ecd38534e17fbdc7688c1fe81e36c7fae93a7be57b71840159855349048c9e531bf9d7d28cf135b8a9141a17c39f96a3ff490bc048ea58e6815f4ca3520de9335c6c7350aa916755b05841193f764c7e4ecf4d0640ff4e8a6178ece5d32a29cbd095333eaedf8e8eed258ba663c783884b88d16fb7c90a04227dbc83414881e0f006f822d9a50bbda1a51884bf50d0b18ac4bc1c7e583816811649c61fb2cf15a35ac188cb1d18353ab0f0aa16eca15515d5edeab23ed39bef4c9afbc1045068cc130c2b30c51454f4fb83201df9f5038608b46159485d9f84290b7c98ab8eeac1ccd161b9a168f71855b003a7152ad8b7b7bad2e613a9a50c3da146dbc7a7afe850da335cc2699ed780b165705df7a842c185cb64fbb97a7fefaaa0e30f401c7f671b3b1d5d16d6d27371a15068f93f0b5b0cf5fe22c27456e172d962891b24eb8729c0d11a4eb5706f9239444f9b4c14b14b17ba74b01032ffb98865c8dc13b79fe8666265838f22a201f81e5656c5c86c229754b98b2255667dcddb07ba1acf0274cfa8a94768a58e5beba27ca2fd9c5d0039f14af1999c2a04e6848e93fa6045d34c7896e78f447a652f67d48990734122f969327255d2b995d404d3b9a09d6b8f35c66f7712caf97ae9467e5143c66865298ff406b31dffbe8e2030fb165de7db3f6147a4acfa7b58ce420523912b81bd56f6fdf06f5c73cd9f0670e05ff0cb17c2aa8cc99e301d206e50bc49345c2659da2373ac206dd5469131361872a2cb3d447093681e547b8d54584ea7408a65e3a79f5d43b3735a0b9a84e9db948ce7039fa1402e339ec2723ba4936c5833c1e2dd7424c8c2d6bf21ae600970748b090ac0e8af2a90705804466b4b3bcd7fab4c7a164bf5f1226e6b8921a200dba6e768c604605e8b978fa512a0db83ddedbfc20abaf8bf0dd1758f629f17a764a69595584a0bf4ef3a6d30013df2be5f8289c8711e447f0181a9e45592a8a15e4e00c69845eac3034828d4e2a911d080e2abd813c1cb845e7643882eb1b55ea9dfa95d948146c298c7484ff7eb959241bb860ec42c1108a585ea8ec5269bc0578ee678cd210539dc361fed8a71a197aec6f938839a6c2e660d0d246909225a75c7c0ca463a4a7b27502c1d2a3d279ef983862f65985dfd25ffd15d94311ffbf7f0a07508d8bb4896dec4fe09ce212ffbcdd9247c87b4431d2791db4d2f32eab18d7a21b5d6da5666959423ea410e10c04c01013973a1086bf606661d77dd2bfff1e64af7eb7f319d03283da31eb47d6856d3a1fdfba5ae748ad8cb7a615bdca78fab459b4fd7830bd217382c685454ccf0716b412788ab528c1892dbdc17174e06c1b81cbbc00b99bd3849fbfeb5d09b2f3546df63c63bed85d1c70f105c81705deb496b509f3be47b2ea00f7c484eec21708b77d65fa65d7e0298b9e18d8b8bd9772f7978e07b2990f12d6785e7f1bfb37b1422eed37c3119ff41d13eb2dcc031233ba14be7c891f199635dc51816df8b39ffc9d62eadc0889c568d5da1a6e4d80f340cdcebb3d3849138492ff70a51fab7f06dcfb06c6c731d9b59beb5f534fc0002808800f96fd49a9ae5535c4cf4bbefc4ba2794f2cbdfed7efddf10ab3589dd33b8c0c4837ab1c8ae83e050dded34ecb038403d5c7891008b8947e74bf73f2c74c33fffa4896782bb300a54556475567395977a4ad15e783b3bddbf353c57704d250cbb0961765520fd5f23bf7fbf449965cafc268ffc50480f32f6dc1434759d418b4acc9aa02a915e840264877f4ff95022408e9b9b49c73c3b58d4eb5819c8e284f03604e352dd1f0005b3a71ba0968512be52d4dbfa2d450bd0cbf221fc8dbb650f0c621782a8c9ddd5387ac50db4ab0ac0e3614c99c8d22669286580e9925c49203836caa400d242f243fd3882eb82d032faeba6aec2c2841fe45d8dff60806dc7817957042a118ada8aa16e83999b2d0e44302bc6259a1c7337c197fff656232b2c3be6b417802249a3428a3494f6c1694801e748cfc46da5ec9fbd0c60d1752780a2b680548837fff33ed98ebdfc69de729653cc4a04b2199758ae8149552a92bca873f0f8b58fd76cb30c6e0beb3bf8a411c76bc2f7d2d40df0c937407838729c5c255fe4573f8e993a04c8f92b1a06b044d66c99b62da618a0f607a7f9cab83536e774a147047eb8420131820da0e026aad7d4f81f45895aaa087da9eb31c48c7a5a95074af995313e11e7489ff2b1714161bad6a61efd0ed64b1418cf389205b49b3fdef0d3d44a1e6def33ca908197a7341746a27a572c4add33d912f56178011cae98e765909f0b74edb18bcd7eaf48cf394b42fa10eae81e14a4a5605d51c97b0042e43f741e4910e6048fa5106e0060568b7b21943c7ccedc47ba30577bc974c29172bed283a5270ce93c124d9e7689befe28fb184e45bf3a086ca2b8fa2e6975b932fc5d9a5ee9d55f3e6242cc6bafa7d65c788bda7dd715d71e52b209815c8596b7c133584a49af4a82ee871247aee249b987e3a52b0dc1238a80e33937b8bf35571d872d10909794a8c133b4849b8f89358f8655f6348128ac575d890348e50d401aecd5cf4816a507c0fd574d96bb3efa72175bb121d9727168829b23ce52e94f50a28d864e545b6f66fa624b0e1c4a9c1e984b1d06119012608340d0c60556d2f61e9d6fd10d467ae2a8a4466b52b59acc77bf3a2ef674841826a35e35ef01508317e4aaf4343f198323a63dd0ddd5fd6a1d6d8ca0214ac11a5f7fc6ef9aec0a16e9c59356d4e2cb7873d25391c8153063b4a7f58d8a1385da3584c16fa3ba24cfa9d838e7dd64779975c85e7f9826cc013888d3f7a1e233b12f334d601ff376e602535902a2209498d3d1b7dd890d121dec939e6aad5c7285838e8c7d6b8cdf4388bd50208b58055b7d11947b0009e687041403521763bd2feff6d0b5f480216cb3d1f80582cd18a3c9f1ba580409280c6e181430c4980371285ca5e089286a0bec67e0895f760862a55102e9643d69a48cd140500a94e5164d5070c88f4919913740531292e15a92ed2cfb80aafa8dc5384fd8876dd1446485953c568a120b64d108726bbe50125001e73a88b82c9ab43320b12b2b763cb3e4a8e57d74b368d2948a3110b25062468427ba1a4624078318f876c40ef4030a16a700924a8b8ab7c4420ee03fbd2b2a849da1a0be19346915c85c542cd064e5dc120b91aa84ec908930f583c97624c7d342f03dac7ec506219e9fe288a5d0ef3e82d02eb4e42cd1030587b65528c97db2c341ad93beee25ba487d5a8c0fdc7149365821a7951f179c4060c47c00b074cd1a232820b147ed9bd45413d692fa7ae1e7c2174031df9d5444a66bd9fd0a4f6fec8eff059efcd0a453510436a1c0f70637bc7ecea47cc81ae6ff124199e834a878852976f9e121551e73ca9684570e2d02d2edc008647af54dd9262b65a469b6ffce84a6f342f990bf8bd00bd97e431435b0985032441ca9e9065a6b0df19b356b1ad921ca20d9dc48d98adefe60f2bc271527e51ff480075f4136b4e74225358bc385fa57501829784908e524f41e5a0202833f20eed29d3ccd143a017f42db1ad5225f4e6b3c5ffb3caccfcd7f57a71611129640322dc8f5022240dc80f316bfc077e44349383df3354e0e7db620c59b67efae63309ae758954c316fbf998b188976918f27c5caa36bb4c7cc2a5423fb7aceaa0565186aea329281e8e801bcd05eab0a0cecdad6fef94d5de9ba9d89496a21b1c7a323878f990284934dec384b276b86e2870d0cd7e26320531cee65b2b51df4ce28e56fd7286a675c79b71d8731944bc5e70707a661ec185c01123a3b835f7c59fe661e64a4c1ac59a1052ea5cf21866196be7393d69f47e8f2f196e308e679480665114449e859b4b7f0d2d2d87f74cdf85ade16f5a4f68ce01158d0c1e4768e0435601201081821952269e7853f0cc93bb0992fec918fabad6d37236adee87af9b861d10aa52e1bfa7721802fb07a379eb32b6b697d0a224813edfe065122e931cca9b42780e4ec0e8f5863e12e309319ad0cfdc6d859e9a86958f44af65a56d54af9eb2df7ab961992b1a5864e523c4c55e86673eb6c4b856c1405ce15698cf79aed76a2c20f48a8bc4defdeb6635420e277d589786092352058fa64d06b44ef5b026fabd03c6f1c01c7c88906639a6065a0949ede9e8d547f8f7918207c62a6ec80052e3b9166ed2ea57227b3411e98032d1392d93e31c202abd921e44999bf22768e22c0145b275ec76dcf3617a51cd9e749ff5605e1937338b8fa61f460ac59f9e7bd421a17403920a72d2154269a917185b0d04c9d124aac897c2f0904457860bd3b95e5cf4110dbc839f204a412d52624e44cabeca4929e437d8913ea0c96ec82b69e503111c3e063c7414563c3e80c611baf48a36101daa3297fba75a6311e0e8398d9f985b9657e6471ed3c01c5c089fe9ac858e8c55dec0ba2e129f47dbd8d66704be4871ffd4d904fdb657611b4cfe0b52f3c2967030bc5de4c6564bd09462fd65ca52431e1c8d160e1cf7c0b2bd34af4063c1e14d2b9393e261365cacae41c80bcb49060c65dbadcff45464b2445d91fe644536a6f063c28848b77159738d7bd5b05fe3a8231568bad89e02b11574016c5fd9153eba99a74ccd4b11488d6e3cc20a13345861a50e4e491f04bf54784ef70c96067fc1f8b622b3b8a606651f3b930025c1305bd5f09782f2a0429753aa560776232d01a518c4e4c2c623e9fd44e5522bd7be20787fd30d3105ad2f32a0ba07ed1a3c4ac04f2bb9b9d5578637e5288945c35c539d59a92087c48ec0e16a771addbce54ce9c164d49d8531a572f398723c9819335339f7ceef123005312442f201534258148c40af455f238181fb0c2c61dcc52790fc4b42248ccc804e130086229daf0f6ea83f1a1f993c634691d14bab23762c3cffd1989c1b1a57e7f345290fe86cf6f91e25369164798dff562a25dd557ab894df4c4db75e31880a3119b49893d05b33949be7a4a922f2594d600ff460bfd6a732a67d5d9004372665b3402183cffbf13747aa1dc442ad7e34af7173abd1c152e28823968e43e8777a53df42a208dad4c552b4561246b82d6b01336a45c46856e8387b1887a44159bcd3e3c832f85f427beff30d4a0ccbcc1b33b33f274012c3aa56234b0c88bd2f5c93810735892868ed0acdf43ef4e76f52195e2f44be1ebc4b81d912aba7fdd1f83e1cc52675da664c39dd55af4a73efa5723e9d27f80a9230dddc3d2f84e62acd9e229441f7c4d783da9ba69209ac01af9b29fa2b23886d36d4e7c380d612424d4b06651b34d4edf56d5f5911cce651a64b8698a2f415d28ccb15663cae4b8d029e73e9335639800118e40867bb5d206d15b90966572089c614d4a3757a6e2203fbd0deb862f6d72928f898d21fe787c9876ebf76df63566120c91a799971e3bafd532a3265a74cd8fb2388f929e929593e0caae11e9024a23f76cc6c267205fad5ddb84102435687672a0fce14c820cc83f8e14f3ad4b5f0884fe6856466dc5e4ab8e6cc7254087488df29d6aea270638cc692d9bf2176414585169b61f69addd4e040259338084a0953374f15e49d9e2d8519e149a4256b13eba5992e57d8df274794c8fc75e2a19bd7ab865207184bb624b72bc2af51504daf81fad16adfa4787e25ec952f8addf0a2819a64d4c3be0600c9188251868e0be12254a8644b253195a8d7561f384a0ae9e2af6c49e2512837578a1f2d2fe1055aab49a319f233067a704fc99a34d4b08a1042701c37fbaa8788b551354d406b71f2560018f2d086f1b8420724bed049cae84760f4d62f11f63004d7c56a25066f9c3bd5c28374a4aceae44d3c5a96b3e154dd78ae665125005d10dbc82c760857280d4a5b39ada0029745999e7a3972873a9797e273a82a5d11125fad8ed258929c89a66e904419a074031468229ffecb7470191bb8ef704e07a5bd42dfdcc99dd10a6019cd652ed1a1cfc29fe5ea656dee76c006d8c221da122db0e6c9cfbbd40360df7dac0565b052f7a7f2654d70daab48a2455fe87e9881e956094caa1f272557b0b950e4aa7156ab4055386a07fbf805140beb8559a41e284ebc426adc60e1bfc3e71384840fa9a82466aee80967b0acd3717fff9f20366a87db99d245838d2bd24bcd781a8353bdc471d37f53d18e6577b9c76a4db803e2bdc6083d7d70efbb0130851c4e93cec483e0d8d3c5d22184a6d0dcc293d66051e9e276a60be5a81e2b3c294dfeb6a29eb5ffde63108850c81ff8e8d6eed8df55a461d370893c008252a08e5b25160549a5153f55281a88b9c413781aad2a4d4cee1fa6b30803808e068aa822229d43a11e7179d835681c99329b5ca6f3aec575a5da277a54cf64e59de03a95301c63bf550408248c9f9bc576851363c0650a0271a0fda9ca16a70a4c8b170dc0fe6266f48b16a21cbaece31f6b925c05cd7231929b874e56a6849bdcd425e70f70ad28be572a8536e3b555eb743911fc592efc66e9fd1f1cecd485260bef1cb71469bb5c7231247ca435610ae00a86412d823ff94eb7073e06a7df5d8f5e9ec5112b76dfec44e21d20d494570e5aeb4b0495037876a0a49dea7bacf0bb7cc9a3e52349a46a6f9325d1c17904c2394ac17f40a71887434b3872e4efd50178994c273bacb94c3043281ac430918f490efa4752bd843b61cc107fc763926b6a3133b01b39a0127abcc54299dc9fd2935d44398c0b2841b2100f5f2e250254bf82d45fc735daecf58f247f79447b3508975bf8292efda40055ea68b1e04b51a9d3fb61d34d2d73f0e6a55df6312757387cecc5f8eb42ea85467ad628e7d8f2b75ea9a53f82f8290c517d2544dc10cd4324d9c4021c1a9a7ba357903ff686550b5fdc51978646eaf36193723c43f99e87ca4053a7bdeddb21c22d58029708f6fea05ce93f68fb26e52845aaa70901c505b92426dbd0a089f9cef88845235216bb4996825eb071791aa3d8a08f9f77cea691a89ae3482f5e40c591da537bd4c424d0ed3e89211732159ed9138ad901c2fe582e3b3b1044e3fb2f2f490c915de9f6ce4fd7e8a4615d885977b6b256993c0975337f07cfa98c3701118667111d197c9006e4bec0ada3c74617dadb2d744b8528d876f566cf5aa7ea815fe4011c282b440957429a311725a7c2b5aa57d9219531f32902b2251ada813000267549b849f43a41096a7fe3b87f800f61354a8b0e705718948cb89530d39c6780e5c98b5080a2abae0828b07c83682b66852c2cbef1b6cef25be18b64fcd5c9a8ddcbf4d08435dfd7bf072cef184f71746aec6cbab57d6979ee87be3c80146392ddf93ae02d347316017ec9a50f104610c846a2880aa3dc68872c84197a7cfa4ad854acd0e0cac2c4ca6a662fe3c03ef3643b91e8c71e362a8a6d6a65875cfef577ff71d8e832929c2de5a838fed1bb9da4d539255b90433008c0ba95bd061f8d3557deffe9ce6e4f37cde133fcc3f3fd126e6dbe0c32ec1404e8e7fcab09bd2c48823440a28aec643c19b154303c4b37d198c90808ecf488db2b2304ee8561d168aa5dd315e9efd39186fe93e054130605371eec5628dbac1fb6c534b976f0058c8c3c29b8ab8234fedfefd411f8afdd64cf9b8d47166c40b3a222f7f899186fa6b9a9237674b7604a2cc83e57416a95b61322fb23fcc2cc75f1c66641032c284d6d2d4321b2e2620d27529e279c44c4ba9b9b24923d00b8a0c5c70985a2d80a319996e8bcc699004f08ac60141d29cf270a1e03f98ddee99990b95f0ccdd8b0d1a9ea366c12100bba959c11741b74b7b66f2d4e079059cb60f46756144df5cf9b91c5302a344ca3394d8cc97466f098d9a64f71187597d36bdbea263349408e362a3d6dd0c6aeeddff4123a5e9dd7d9d831a173f031da00d03f8427cec12952bd7240b10a199a9051cc8fa15e099e8b9b77f88473f8bea72213ca181384d09cd88b1190f5ee6ff1ae6ed45a44e1f1c7c4e41496d4a3ac0552e1d9491d710df5ad8a6068df8381b15f50f0ab1bdfbb591d9c598e19f120d30b634d0878a6bc74d8251fd069aae4dc602f4fbd0dfebf916bc63aad6c3c2eea4b6673b76f709a6462ea606c5550c25beefdc797b1839cfd783e52e5b6ba0dcf02c51b676a370bde6ae9f9858c11c53ab6057629f0bcc99701739df1aec8298aaca54bd5ed509f92a2296cd2354cbfd0754aa079181829078e7410756c8c678b910bcc2970db808fe5c224bee6338453145e1ba7d5f055933fa0fe159cc400a16ca5d7508bcbbea1d5d4e1b0a871c422e06884fa4a268d0e1e8d212867b682ba643796edaac60fce70b8828301e78d03951e9468986039f7ab3d48a2dc2698f43820178e7554977826837077b7ec7a1244995d68b8d0b8e5177de3881af8dc083c70fbc5aa427094d822b37ac722d7eb966037200107be4fa61b262aea2185b33a84de8be0ca166a83fbcddec41528602210b3e729422687b550d1e3b688294f5f943ee3136ec9e37b7bc6a7657ff86ca0ff43bf40a2f6e552ef8970b209ea9f94ee6cf16e810db67a03936d13aa5889d6f6549aaf50cd95dcaf1b21b9c7cdc41cb4f1cc4bcde88050f0b92a7a9c4ba6a58231cf6791691478c73f3f7a084318e9a90101271c4d9db5513668d168971199438a14517583ee34e011d82a756e4809e71124f06297e10dfbc85a1a2aed1e477e9a01b122192b5551e2b4f63c11a6e08792322db3c48a4d72bdd22a633368598f12eacf5e6f8e7c3ba6fee79fcbc219c4148fcc81e39e16bea09930cabdd34e97784a35d2b2ee3a3a3f79cb792c02380229cbe59481a5120d1e74fc63755b925eedfbb599b2b9f2eaa9759463219ab4462284ab30dc50f676efd7f9101d6b30ef24de97a0d0958324d3e2dc3c0c0043f37affa601037b863f97caa02654e9d8a047c253ed37a28c573b73ce853c9bc03bf1e6b4922da36f67c5b2f539d8233115c1c74def76460d7c0f16fcf0f67e535c64ff1f4c502eea8d24102a92b2512670e4c62091ae8e226e067a6914a516316d57c83c1d6d254e175c685927b5daa8ed8a462ec9d147e43db530eaf6ddfa906e4054a0bae8a0f44e7c0eb56e3eae77a097c25808ba5699ba6590aca96aa9f289abdc6caeec6d661d5770c4df97cff806bb68c000e4bf5a9184cdf4ac2898ba7b983bd4f8b303ffe374fdbe57630e214e8b27d5a193fedc96ef532cb881becf902fde2fe9918fa001a9fc356def6b592a6191a5d9e7546fbcd74303ff7690b14a87be30e70cc2fc0c039f344920063f599be2bff93c976314e25c36cf76946d7ddef759f4a530ba0f441c98b7e9f5aca45dc1435fc54ee9d271e90227fbbb1d29ef89cc5e4a949f35b17a97d3d0b96066d1cd2b486286d8755caeb981dae5ae2f524047bd41ed1870924cad75890ad2dbd4ecf72de35b96590b3d3f207c2f00848210ee5667fe8a1d722466e0608ada45ae0000c51ab017e8e8c755bdaf92296c8ae216548960f46740bed16e174a860ffbbb055d3226e416df9b55e5b5a5adc968556aac5a2556fb5d8665fa13659163967166fb7b45bdc2dca167a8bb545d7226a4169f1b75c5a582d8e16450bbdc5d2a26f11b7a0b7f85aae2dec16578b420bd562dda2b788b7a05bfc5aae169616b745a1856ab168d15a2cf649a16b51856b1fc0633dbf0071b5e5aa556b196951f4c3428d27b1502d3c5d91f2794bba75dd526fd15886467b3e60c82ee9c188553686d4b389e20237bcb18a84e13484f7b7234de16eff26a78bd00aaca9c1578626a0a4422d09de9a60b17fa9f1b3f6b3e6b22be967e97618ef8d2fe6e1dc79a2503b26e0005a18fa3674548be902051adc4b0111dc43070b5884030b5884018cd8c7010a59c4818a58c4818e5904008f5904000bdac4810759058047d9048047db050084db050084594680a348f2d043bc1af85d7a1ae6dde00200ede002006d2023e07d9042007db022007db063000dd063000dd082207df041400df051200df0d1600d10e1600d1061801d50a7348e476a041958db7e91eb14029d43a3135cb27c6f1fc29cff99616ccad57dd2a398599e5b756b0222f5bdc19c0d6a1ac4cce4016d644279a53afbe5c734a44b57b521ca5667e72280dc0041cb0d7452625f32bdb00f90c0b3f983baeef516a368ffed64f2e83010c01e05c9fc857b56e1fecb04142d4a0ab7e1938f719914b5b5a20063e30f056b60a0def303cdff9df987c2facbd46bf259f33b667e50b0e602f5888367ffeccc1f4a7dd397f621fbb7f389740f685a54725e6e541311b8f86848e0ef253c3946d28b8c036b80c1dcfce392b517d5af2a42f6bf67fc50a2d6c5eaa8e6edf9edcc3ed41aa1af7fc9e62f9356eff732078ddbd0f9ef79920e02f9d84bc02793d5b60c8d814c466c4ba666540315381b7faaebbcbe9844e63f9ea6f6e1a0c67bdc7f2e45f1b1e1021e68fef914d507075b2bedf87f2f4e1374075ce9738a3983114ecce5f62f10a0de6e64ce429317eb02297c458806da6d3227024a3083c8cb190a0103d43e2c811fd0c95848778a2734004f51ceffd2069ab3c3feef3c7174980664f129ec380757aeb685eaf3834685734f53dfa7898571c19dadd2fee503b4ba038db3f0c4d8436be51c1e204fa9fd71e0341e1efeb22ca514df3124f288f59fa754f277f9032e9d58ff3b630ff5b52f5357b3068ddf9ef951c13a0cd4b37547357737fda1b0e665ea1b51d5f88fe9e4692e134068b0147ec031abd84d601745a2f20b9e88c9a822fbc401c6769203018d5625e0163e91312f9652ef730cc06ef4419d1a8cd07953d4f59fc9a4d0b820e012b920f1de869288d7398db5f56eacb217b0c8b8814c5acc0ba49bdb860ed8cd7f2aac83993a2bbe74fe63d60f056b2e509f5142fecfcefe51a9357d9fa164ff3b99381a4c0334c0119abf70ca527ef722003dc940c80d7c32e65178ee1603c0a4d4fb5de60061249abf31f3b382f52d57878cca86ff8371467dacfa3aae5b87311e4bad2745cdeb16fbc1feed6c321d01da7f11cd8b331522062cab5e8a33e0c9c778997471d42200639f3c17cc045a922d891b70cd28b8ef0281bc8c2207bf97e1cc1e50dcd0d24e4ffc531a29d8abd0190344eea0060a043c66d958cac568f657525838e03ee0d8c9953c0f124f2accfac53976653869f0bc4d02e40c3e426001a0d08a8cd95cb3e6bfb56f8244c6c5ba3a806596f70cd759628264a4fe7b1b9004de805145cc0e318cc0727c416b131cc4a91dbeea64e6ec631033a779379a8fa155bf2cb497f82588235200860d83c3bb45e44c073bb708ad8d752b7f890d32702b2987a0e6dcfe655ff05477e0d930be01408913baf12a1d1a9fd2c8072b4da7399fc20663c071a6c80fda4e66349cb20aa1b77a425ae4e05985c31d2bbec48a11388c113af4af53aa78eeb6467e995f79ed89c3317a531104cce0a310e8eb72851c31d01f11b66ce5c1b02b49ee7878761bb18871fd4bb8f99b1026cd7b26edfd23843107d41d7206111631f33cbabfc93b15d6d32f8e03cabf17532b352bdb4ca0082679218b50cb000e8204afc1be66cacb2627ab5df04b827b3d814e754442b419f6857248a7b8c58cd26f45ac6167d872ecf39cfc59c07c4401ec6c2ea299dae7044a8be0284d1c8c50f5e74191c797840a94e3c92d5ffde210db46a0a247da2f9e0dc672c1d239a316b20c3ebb0970186e37e81cbd0cc4985a78ece6f4985cc948e36e50589bee8abfb0aabb8fc8610a3c95cef53b95451e68ba34652a29e0d0ca45aa43275f488b600b62e9a1f5fbf3acfa760c2bdd4a76e42104ad6e7c5a2023c2fd9571f2c0d979423cd980c0d43ac5e0f1ab7831b672ba7dd9ecfbf4d48cf641f0027e0ce305c2cb37e4080216ea34baa4ed842fc9a12ddad4cf77d3f8e5b5babd027a4e8b8bdc71d6a88b660d18130cdaa6818c64bdc7da352a27307e70bad9a9753b2d6115472f960343aeffbd023bff34a4d7c8ac886bef8675336683b8b1d45b73d1e4615120db7e6e4016be6e6746a4ee3af58e8f506d3b97155e6ba0dea52278b736b43a8376dbbac4fbe11a5e426bfd18107e8f544feb1ec0060a3c57b100c891b52bfce629d51533ec1e00d0b312666800158cccaed19b9cda0f8dfb3790572495effa701d987495b19bdf40e11b2ef2800224e07187501f10d265c039b2c9cb5581dadbf912793d683bd1a1cd957474a526bec62ba347a60a40aa5c375432e905035070b5799432df412a87d4aa23930da8068f8e73e038480e8e13e9e071911d1c3d8d2a382ab09b618301df9535d33aa972c1c744b824ff40482d58d174c0aba0d49285a7ca2e3053740199720716934e7b02600c44c514d374406392a90785842632eb109715e83b703558b6d889cf3a724517282e9b7a57483298acd40164850e202937652a885c5148ad3bc97e8150af0a8bd44205e50e1c8328cc46d49af3ec1314f66a22d19d50570d849ada8098e34f08144003f03578b2cee122dc8296e54b565a117967a3589768dc83c5a4d39e007805ae755399898b292c30eb2ec23749ffa00ba0819de5f560a49fbc5364628308b68d11b69bead9f9682c8e0b6ce42799060874c11f15566c30b9b7867604ecf5a36806c4df499ac091fa4ea283f49284f675182a1ed41ec84aa34e0c880db4ceb4b01cfb90a5fb2b44124f5c42942fc0bff29c85af4d72f8b737e6036634c73d83bdcbf538ad5f2aeeba90e9b9fd7111be87422a96c0bb92e37d018cc7d939879d76c13801005d25ca57ada088923c944c18b8c268b862e65375cc7211012a258d2a10044d3d3d9ecfb4b49b3feff2749a642ec088ed4c2474d9399c046c113f0c723bfc284c822974ee8362c9c9c97e9f6d19032af1224b3d73e5d23596717e653ba25f224978534fd2cf4bf409a44507952278270dd795a4ac0d309edf69f659f7c1a48ec6692566c7d50dfa6b0624ad58199a591e86df3aaf1cc622d67dd73b28683c8ae598b31c5d043b6ad9a51213250b15104cc991a7a89a80456c14a0c50c9055420606f8c622d657432c8baffe5fe1e8a9ebb77bb5c3d995c170301509709760df3d0b8034c798e231f930e4ee165aba1438cc71d117c9e2fe11348186fa41c3ffca191102f25de3782e671e43094b537fcf345bad9674b5681773c0a5eacad0ca7cbab9eb6b5d7bc7e7350f4f8bf63b637dcbd52c572dd754d9cddecdaf30ed7cc2e0bf493dbd3c832ddabad97b61d7984b1417fd5c35f7b989379b3ccb5d76ae1ee3261c575b2e5ab4169116548b3fc536ad96b75d17baa672a37d8b969f10e77a51a75717b4bcb6451b33a9ff3d5cb5df84595e05d2b62eb04d764ed0aa40078fa27571a56d89d1eae769f573b95acde2dabb3bc9dc5d0bef6ada42d16576dcfc95693d276c1c03ad0908a6499a652f3b574d6e16fecd50a77a376d68c2e393919b8e2e4f1d4de6f21a1786f1692ba6ed35372f9baedaf64c7bd79b9856d6656e93929d5c68adb6b8cce84debb94cf8c9b770178e5cdd409db8610b3427a2935a165c9ede80905d04d86fcdd83c097c515db5ab339ebf70de74b0cb5ab4f4a7f6df0df8896fb0d618bd6e1e17df96cbc56caebd3813c899e6362bfdd49aeef2ab0b43fd693dd3869dbd4ba8ab767ca6dacd784c1bce6d57b2f67cffc99652eb512e17fa0a03991a5718e72658cc8e4c22d4ef796bc3e1aea256d416fcb92e7c4d4476648b564b4fad2ea6983d00f0660da58784a2801aadf32bea5fb0897752b87d322463c915e9dfaafef546721184b9b39259f9c2b68cd24b7eb1023dad7cd57640c985d7145ffca7aa7fb1a10c2f61a60bb9fc1920b5146b5132ad037d11beb15389f84db920b82ffd87569a54174d607c515d6d7805d94ea5e453e5e2f0036f58f29e412dfcbee65a16b2fa5441bef00b0bf490c46a284abc19fdf83e4a5e9dcaaa9487a456e68bfaf21620c24b83e4c2bd44b369d104c65f6d0748795953782f56591662ec833aefdee94781168e935c7c2d51b67831d073dba824f6d28aadb8b80812c6ac5d5d37e6fd17492ff116bb038c5f9721c9eba0bce626d7af32d28759c4141193be892d3ecf60357931d227400fa8083f14706786ee32936147acfdeed0dd61d690e0bba10b944b8b7fd5024b52c55c46ca71dc5b549aac13985be013df5e974a5cf0660274ed27ec29a473b1136df086964aa5276b91256f91522856c5ba21bdb4efa3a9a371bad9c32edcf606f2781fd4974ee47ecde5ba8ee9baa269e12cc728859ea692c890d5226e5bffca5b8467bbae2a232c7a02aac77d5c5d301422a50305a0835d49ff40bedd4ecae6e88a01139941c9c6c6146f4957537e2294349ce23f71aa9e6f1e56d476db06b39d8c6718bb3bc66a51673bf501f82a4dbcffca028d4deb6d58de359644cc26ec9784ecbd3791524a299394015d059405f905302c37bc0a86a5884b83381a84332c4fa4defea584a62fab2863ac90698794704674398d98ee80ead0af7fcd50e678b279628d301cbbf5fc48f8d26daee02fd6f0f5f049d23cb4fda2606c3205095f1816d77bab261f21ec88a69792b36102f737e7ec185f2065eb9e9bceb16eed8dbd79eb1ceb365bcb8457372358124af0d13cf1ad17a960a3ab097644938730239e7c021ec28c08f2933a9daf6a9eb8a43a963d35cfcea14ef3ac61c304969322813fbc4388476c407cea2390d03cd1db2912864ddd84065b3a09ed69ada1503435344411e52987598c31429cf2ad235a014715daeba2f6ba078c21d45ee7b8b104248a6790b4d73db186ce69bf69c7f16c9ec090468ff9f3fff06fdf7c7c1f6f8f71fe36d75ca3e9e2d774364de0c844c62734deddd0e0bc4a6535b4383f3be578042969c8c4ed6d5bf06dd2e49526ebb0b91b3f0f330af1896a5cc70197f8934b292595271dac284f9481d9d97c655783fa8f44e1e8836c8e6eb0019bb011fdfa742dd5c9276fbd1534dd3a0cb74cdec690bce692abf1a285740863f8c9b14b4e87f56ffae56abcf0d9b970d971f5ea38fa8c0bc1baca6fa7f29a6e4873adf5c2699c76ad4cc2f0ac6b05690ec33587915b2fbc1594721a4f398dd3bcf0d9a9565c08d3eff5179d8beedbfcbaaa453754e3f063fe50ab9a6ee83a4dc6ed5d9f5487f31dbd964168af6f6e9d7c7628c497ae9de6297359b47de6a7a69ffed3a7134e5e543febf2730a80e801d32f0781a3283f33843ad72da7c33afd9136ec0663d03c65e07e0833c2f5d1e18f15e6741d347f462d1bd0672632faef554002e201d743e841ad486c02e184347ee9d82536893788704e386d68309b6613ccca96296686a95d37843de99c6660cfe7eddd0bbb69e4520e736afe8c525cfeb6aff9537dbde9fce17c752d7f5efe74bee6af3aadb5d69deadd83330855f007020d2d47f3683e1d841f1a37a8ed265783286e9b87d0bef9d1dfee9b32be6ef7e4475f7bc8f853a631729566d86508ed66a545ae866725bc271c9f526618259482e33ab3e78c90a24e7132d14d3f6aed757b6dcf2a2975d9b27bd2eea652ca8ed1bdf3fc8183b2a72567a305731ce79c0d11f8c3b932677a9c4f1ca9739c47aeb603da01490ed40e903e7d6e1f4f47dadc380ee559f7f9f8ec46dc4d407a4a73ce53fed3280b70fe23ab6fddd16b45f34fd56ff7236dcd5b9c4df86491733a35a266a1b4ebccc274a5cbcb2695cafa4df36cf993f152e725ce4be9725bcd006f34393e7149d77554becee599bf9dff7ac8f8e95f519fdc79326fe73814c771fed3a8ef08663ae2e56ac8cc65e799ff34e4d13c31aa309be46c6a2f7ae699dbcfe208ed7135361e9d08383c64c7954a868e7bedd97a6aeee11354a3a40823ef21a8f0a4bd8612c68b151c2d3eb1cab7cd18bcb587a3c46dd2962a0e4ea723f347bde9473f71ac9d76ce39e7c4a99d0a7f3b5d742cdd69afeed04abba3df11722ab4d33c3b64f46f95d43756f0f5769c996d6280e391efe6e39178241e699e78a4bd8f44570837edb53757c3d369af6fdab682e6579f5fbb55fde8bdf6dafb7eda1dbdd73c5ef3d41c027c119a8747ad3e9e4eaf56f077bbc650744a9c8f192adbb22cf3cf7b529e6559d539ed1c0ae599731c6a83cf6df8082c2795d2650e1a34004ef99cff4ded9442a572917cceb920c83d8a3bd1efd93d51b1c74c7aedeaa79f9ac0dfea356f545f557b5ac6ed4df8d9eab56e273654d41f67ce9cb16282cfd33c34bb3f707be07cc65b7b6f7de3fac6471a1f3ba2ee835e6c58a2f85a966f0f9c3fd5d984b789b7286d9041f3509e1f14ba768d856384b7b48b2fd45edc113f9a7f68b4471b079c11e10ccb11c533118757d768bad62287bd3bf85d3e83c15c2fbd15d42ebdc625ac88a6af7d08e5354c58f5928ceba114d7cffc2222401511209d7504c7e942c5128126557046e6d2da0ed7a882b2169bed583260182fb9c8eff2296c2ffa552ee08c13a2ebb18b6691b3d167ed5ab1da1867f7ddd6a5598d369b411687dd81151f391d47508ab81e85a3284d5c36e413d7cf26ae9f994892717d9651dc760447ec84064a594f15d26c6f7dc373665915d4a4a88a42a12c0a755128d449436528140ab509a13e0c6cd427ec0a2d3036d83c71d5737a36ef969d3880339f593dd55a2bb55c8de699d4379fd35bf3d9de2d8233d7321fd282369ff8ad755c3db6ea9d766a34de99cdad5fe3f29a7f994f2dcbf37e994f08637c10768515a7fbf54fd7a249295b388e6eeca454b794dd2e636c222329db83649e3776085831703d785397efd8d4555e7e4b76ad997750f996cc153802aa0e6165a23c849581f23654b004588067c8d42c04cd571e00da58e1fd204283ce4ac2fef98ea4541ad634a41f4ef909a6ca53876fbdcae9d6e1b74e7e1db6da61bef2d22797976ec3bc1c63467a15439ea61d8c13301e781ab91c4751a4c5a1f9ad555e97a71a7fef3c55d72867a3d6a1cd699c266ebd6dfed3349adbfc65aee52530fc86007f499203c1c82b64404bd6637335ae7f3db2b79e552ee8f459fe76bcb45cd0fdeb9ae5709cfea863c376aec6cd4399dbcb8570e26efc6459e697c391d174b5d2d42aeb96bf3b0107828613c8000742109104c8e2b83f645dfa29c751142db33e3d4e6fd74e5be46c4c9732cbdf09e2c89c734dd5cd28ae7c9639f50ccaa39c260a2e8ff21a4598cf7c72dd900b87314e35bef99c4e6d4cb7d86dd70ac23e1dfbccad175a5bd41076cefaf5d374cd6bf7451d994f9f7408723a505e73eb456ee15039769563c77ef429e73aeab3436df9a3ae390c87d1b9e8565ee7d3b5825038ab3a9c1d1d59963184ca20380b0247515680c0d1d1572ec751142e04a746ff6385d3d14bae33cd8bea9f40f480dfbca83e91fdeba9ed079100721c45796d85bf593ee59b3fea957ab5619d3ae470d8bc7339173e3d845191f4323b0d0fb02f55be38790883a249cf43d813645e4b4197b19303af98be7d88efce6997c2b15ac01fbcf2d323348f10843dd1e5dbbfbd079c21bfbd5d9003af3eec5002b5432a6af8f6c96a01c7adbd1b34314d370f6162b4881942cc0f380f616288e8e040c643d81428804d4183036ac0a2d2a209ca04618e8449ca0215468916446c2db0c181f510268590d643189830af8281d1f22cd60caa158c172e542d6a6c50148a061d42a8e261156111c9028bc24c3bc4c9d2c3cb6ee932a73b064c08a38c31c82e341d78c24a83a18d0f5ec74f08015687b3e6d975acf1042933110a474ffdeb40191e9c79147acc132e2778fa23b5f9a9f06de383af95d239a5ec8e3046ac390655cad9f8e0218c113391d147d16012567e06af7510ab0ea29bbe3f888793c85357a9f0453207fe565435690bbe1d6795c4966fbf514d0e5a1a9bb7f66296a4a7592551334b26b409e18db6133958961ae08a05598e68e0a809d288c2023cc3032d7a175145340745a696e686039d365449820e0923af6a2f763024904e1b994cccc398bcbc6a7bc2e5db55cd5120aab487f23cc9801530313900742d455580db044c495d0ebca24e6058c27cbbaa0665148f62db1d6058ac3c74c8499f326e76350b67700e9d0070066a3bc92ee5184b78667637dad69de8e6f3b7adf3da4375048031b68eb69f4e1b173477dad3b19b0d96bbd91c813fef678417c5651c1843de1e2ed374b773a86f9a6ff3351cf400550074389dae9ddf1d9c5f3b88e9140de2f497e5ca7d98c6bfd5df7bb78d524a5d4ba13acd693acd71a76db79b5afe32e99b53b1f9ebe1104208a13c39ec3ee8c5e7bf1b7fca26f83ccc43d3a78b596a3e7fe3509d8cce69d7b41d6f193d3a9d63fd0465cfbdf9f3ff64d8b8d9569af58c018f4d11f873cf26863ff776baa0b6ecd61680c406509b223c00c3f2c43e846169123123663e3eab8c17c17c8c8eadf8e82366444cec6e97314e981a008e0ee3f644831d3312e6a3434e8bd66904eae482a45b692d96473536098c7079183372e5838731233534bef4a6bd86312345be070275b15d0705f85b358db05110c6902b2942bc3c9422a4e95b349f105f7a4ba2cbb924c981a267415a9ab8e8db7e4a4a296d29993db6ef96122a9aa0171256382bca24c20e1e904c16b5886bf3194385e633065a0b85143e63a24bf53b56ee1551d26483c8302bd5cd14d65a7bc4c30c2d0822840f62928200d91f5958e044145384d0a10a201bc55b5fb9e02d9129041813660892224360d0d4d474634494c462e8c1e73f9f6b6b132c4820430b4dc0c0092049811ac054e145892660600292f4cb4b29a5a4ac31130a28b17dd54484f85ef97d1823e264225591030a45a44f5022a2b0c0c29618a09002049908828612f060ca1337a04e88108ba10696122c22a2b7a0ca0b60c014679632cbcec8f5b5fb1ec66068f2ae87390683131b7cfba66a8f859f5934d8302489c170240603526c0816b1214d4dd389d8902d50aec45ee0127b210c852236c40a98d8902798b68a379517e9526efc6caa1b1794f80f6749eba2b25c82b0549ca0a1bac1b10f632e24e14295a62fb1a42db12424624955a4d0e8c9e392eba70e69a95da8e161ec033dc43ee07aaf4ea97bdd735d45bd73a86b127237edd19a57cdfad254e69d2534d83d5b94929504752ed2c6c08915e24397963ca99c487222248827b4b8d0c3a5e20492924f3d8c09c9e1b587312148a4c48424fd7d181bf2e4616c48ec0533b11690bcf4feae7065e5793a16a4497e96fb9dc47f322ed4a9aed5fbf72574d5aabdcb8461ac0526ff412b9e42fc4f56b1f9eb1b34aa0fcd3eedb5cc3bd5a57f1e4ffd018145840f083f2d68a7e8512932494c227ba4cf8ee7ab2c7f3832317d3e3b3b9eaf7097974740ed9f440267c09743f191bcd15294de4cd6a9abfba22b1a4517d04f91fd6abfe6a11a9d89e0996f4fc17ee7ef48c8b7c7fc250043200844737401492c3efa67bf2116df379a2728c80da01538c3be740879828238013df81fec02400386583c048384c5953332ecc08815a0207e908269cb0e4a470401021050c4a0c08a2c4d2f20e92bd92369004203104c51028b2d37000960081da8200a0d8264210590741fd90367175b031c2953a6488921c91063214c0c480e6a520cc988640d51f33086e4faf930c6821920562004124066b02a503d8c0169e2613c8c01b162998008a939458112f3c09798ab49084bb1c43c4026a50228406240760092c409cbc9dcc009584b3e3ece4ac1ebc57a317db4f23464cd11731d819db181d5845e7ac4cf984b498ad5c3cad389b99c7c011ec65c457c7d187351f9cf63da416b77ff9139cab5eec792509651fe23e5dd2d712eb351dc3a632c73ac6b480dc35f6087e1d88d96845c788c9e72a1e8a994473f65a195a3b211ca577947365a127ae1311bedc0c82fb2d14fc6d968c7c8658e7517d93ae7b12581e9990bcd931b693efd9485a4733666a325a1762d1bf990991b2d0945a1cc3b1b4597ccb1b163cc4623c81ceb9a1b6d4eb353d3fe4597ca21500b8740d14e08745fd5192ded68918d2250cab1d7743f34cf9ca6fbb124a4e598e4470076c0399563139963ad0780075c7f182d4d0002d12cffa00eed7454273dd37ae0e56d1ec2cec0462da1ad1dd52d10816816a5c8b63f1891032b07daa7954ab26eb0d6aa626f7d756686b78e63593a9caae4a0a1bac13183f4ac87303340fce921cc0c12ffe94c21584b707698500a0e0e8b098c074c303334602559452f5e819ae2c2ca7839b194f80d0b07fc41261e3b56ee710f616566f0a987b03256fedba94cca282973240ba4873433607ba9d9d27b8a882184104208218449681ba593c6f6f2d33b362036e08b5e34ffcbfc2bba7fbd68768c9d653273686db47ecaada039f4136e98d64923fd32975eb3939574ce79e399bf990cd2db0c23d0f7146b161a1c414996d5c86ab5b65a6b6db5d4a7f716abb5f6356d6e51cb11726904c83b379a55eecb7c089e4e44f0c9331fd2e654cd39e7ab9a9ec87e0a352eac4b8731dd762b94964a1d412172e6bf23282ed7a3209bb8dee613acb87eba5c5f94057107524420c598978dd20c50f08a51a63039415582a3a474c54b014a3d10d280570b065ca50f2811afc0f0c2f202f3c2018d3c88e1a554458cbd94b2784da51f4010491089e0e0650919ccf0320383b885c71536dca0c40abce050850e310092c4c30b395071e408144c8080595eaf4c4969072178412cb0170f93004a4d26782921614416d0880c39a861f2f2e1c92b0904482753b06006174031464b91116858820c0c5e20268a0c21384245639225d66a44042dd430c1415c72a283235eae266213173cf1e5a534021788407ae175431c42435264c1aba134932a5e57bce09595945e4aaf1c8cc0525e36a834508a4370f88108124994ec406609305a90d89205941ea8000116353c1c79d55000267290bc94ac7829c12f1f680902d622365826910a275e7188235e569498a278d528697939a5f4daedc629a5f46eedcd2236d879f27acaaaef524ae4291d42338528a383bdc0922ac8d440065a847002a8c6e0448725548c94e00a19d41862478ec8984c79cde0a5146d28c14b89881578e9212ed1255679bd16f0da945e3294a278b50030a55600509994521a238d944608218410421821842b90f1ddaa696d96659994524a29b3ccdae8030641055be4c38de00c2abb938fb47eb2b18bf5c20306412836c91d2d93c421f01787741224d087ecb9c143f6ecc81c261f1f29ad2545d473bb8b29e993369748d2d5b14b6c2287c0199c6f4102a5783eeec91b4ffd5e9a7b78972d472e5b58b00b924ff5295c274a9d3973a6d5047fd1655d495a3d60cebf4ed23c77f386567c44c275278fd0cccc5fcd45f6698e4ded45558e5faeb74e201bd71790804df1d06790a793461f9286aa7795db2625e737aa73dd7773addee2017f904c982f2259976ed43cfda59b6297c8256e69cf0b8c618599f6aca8d55a6bbbbbbbdb7e34c3654b982f2d25fcc52f1f857c277cbbdd01191f4670c6298b012f8df662113b3e786a1c7ad619c91e2590a0e3aa1b397a540e5d04d9b3d334529e7526f8fc10761ad768ba166ec2d0f51acf5af877e36bfca7afd7641e75be45feaee3af26f7f0f9cc710f9a0ee71248684f07ab6247d8da017ff8e6cb9f3cf3ccb32bc463c78d1ced893002c5a215f928810408212ca1c774082184d08578ec28b22fbdaf2cb2f96da50047323f547ddad8720f48e676b5d50447d7b4cd6f07c9bcf5d60b7fd1159b9a87f3a2ceb99e694d5cf6d1def5e9fa91365af1271320999fb9c8beccb75d9da587cf5f9f39e79cd3041f78a5e37899ce8eec41795d556eb677a1cc91395afeee8eccb9ce755be7ed5dd720a76951cb99d7de759cf6eed4b9d9fa8fac1df5ad651576e26d429fcfb2ac868f6fe7e74973aaa5407f88fabcd07d65edc95b4cf0b7f3d7af6bed657e73fbd6ad3a27f39bd11cab9ffc662865be3413e09709af4c13e0971f825ffcbbdd07bfd45417fef9a89aab2d6aa5a9b8ee903eb2a70499d3ee227ff855d95be4fe1d72e7a76f256cbe71a70ec218341dee26aaabf2d337a8054fd7987e7a66afd3dbc1b7344355b443682a604d7247345aab1ffdad6e7dcaea9456cdb672c0f32d17816d72ad1cb6ed0467dca65b0a1d655f5b5db6fc210000b8696138351696bea4b30d5e21f60c6d85e1ac53d55ef56fd5b58ae05a6b9d2ea7b41906a6ee40e4265b44f0b76d58b5ba79817fba8cc6da0176815b6836e2ab68b41618ce6fc52253a96b35cabce4e43f5d6baec1484bd7b3ca0479a65fc7dcd3ffe906a3d160941631465a8a79b6777b01ed484bd08338cfd16b308260f6d16ba918694c45ff66507336a6b74baf5c100672f743d3dbb5149e9e310820ac1042086137695b872ebd53b865f37dfcdf65e9004b2592daf8b0e2a0b207c24f067138a44f875ff390ccb865021cafc8018a8338f762f8652f3328331843eeb48ce04fe7a5ebecc8f0a1dd09b1f3922a454813e466cd1b94d23e6836e26b58ba0bad21c410cb1b3f9c61a4a59a55edad7a0172763806dcbe750bae87b122615ec6c35891274f74c23f0d71e08f7631d292f420be1461a7d98aa1c34a71e607b2ef855148bb30a733c4484bf1030dc24b1324e5069554a8548df6a4490b99a21900800253140000280c08860402a15038241a27caf00314000a929c3666401609235990a218886118866100064100000000000163884246730fc449512ca55e4abba640bc6e470ee9e57152a025ac6b3148a5d49792ee499768289853b4dd1130599988a3cc28a1520abd14ef29f428d77b6238beab3428ac30a165a64934c4b3bb3eea61a78354d1a977f393b84ec27b7f02ee91d68e4df996628572a529c0cbb9263b704b5127a2edae40aa8ed2d0282f2c7640d5ac29dfa3a033918362885cda29306276eba76c39dead41f5f0da6ebefa8af2aa28ba94b2037c9bbc7c3b09ed9eb609cc072832e8d36ff2b2f551d59f3e5f27f0285118e86692b8f74931c40376e253c3636c9475272835f77252add9a8ef3caf6cf17a27fdf4a2664756514b09151457a258e43d516848a9d4ba94fb52e295a258fddda76a24455714d1825cd139574618acd6cb4f283777a3a09215e59713573f2192d7e964523b7a482f0fbb09aa7b5c7527aff2754a97d79d5026c19e52e7f7aea60a8c22da1319599de9a5d4ee838aa3144a815b1ddda4d4e907efb8e9a456ad55dbc12b2f53fc2a05c61214bd59919a7c73c69a0eb874bc725eea76ad79e0978501faf237ba2cc151692a05bcdddd53b519653d8a3dc63edd835258c921ef5a4a19046a23cafa405852f51bdefb72e264cd85f05357401195f26a8a25efa7cbf53730a4284ebfe3a01ea7ec1605f414eb940b5e4ead8e58d54c51cbd19d384b075751708a7adff1955a2917bc31a9e8602305fd94d37ff2c2c9bfca0a28a2535e4ba1348adc7e914a235230b5bcc9ce48522a527486342339d5c5d45da7cae329b5eceee2a47c99bcf07d1575944bbf2696ca4757579cae5ebbec57b6a0edcc56d529a2a198f594c4953b26d506cad34fe1e2bac98b4d93e93f65267d584dbf4fbdf69f4d4afb738dd57bd543ba1057c501e31d0175054574caeb289653de6dc2c5c671528e92aaece2d55c4f348cef52d4249c5e77f84f6a517cc90b5e538a39253dcabdf69cfa6374d29fe1fb5fa34e163869f0acb9345c62700a8cf49d0193a8a4d8f2e89a5b5d541bbcd59bfc577ec5f5492dc64da814a9d8795667e4c1d82045a7f50e71d3967e9d70c19bdcd8b929f332013d9fab9cdd8ee546f63a6c657f6801923ac54bdb0130a76916ac3207fd13dc03e08ea4552e02f8839a03db4090bb951db458f3df1eb466b21de2782621d7e9287bd5ccbb5039c28e48283a024ce6f05a9d08be362e22f380e1d10f03e39749e4b93e04aef7ca5ac15269460c263e029cba83bca3716751a8f03053d445a9bdbe3a9977202c863dff4d2008a7935e8a6d82809fb2e5fa2e5e7539118fe25452b693c4efad27552ba87a8753f222e52f934b0d2d92550612d23bceca510a57caa8945641a994d285e7cd827c3ab8288a9ac01848251647dd949ebc7ce8a84aca8bef091f7993123be11233c51deb193bfaa7176130494ce044aef2a8a6efc0915e0c7607d4af4d60e6dea76bf56faf262fcc0dc7c16f93ac6390ca796126e788b749e604c2c4b9d16617b85ebe179536561b8a4978d79347d1767eb9e9b0ce9080fd73c5822c4aa24ec09370302e64b3bc87c035339cc8048e61e98d2e43675aa8b8c284ae5758ac1b1010e930727b100fe28110e0e3171987f9da3b24faa2b2e24beaa8a98e51c61673c41151471661b52cc1545244fa724a1e90405d42718c505673a80ab4581083d7babdf6731a1f00c24c3f5f967e82a45ea7ed8e93a34d8a278783ec48c282cb2da44d453d7a6e69717e11adbe314fa448ba4fce44c6d2b76a4c5cc4709017ec66be9c85094753a15306362901bf5ee09399977e866512be0d507fcec1cbd01067846132d585e5d205cc15231d28324d05bd0ab17e3385a2f4cee00b5b649bb50ab8b1e18956c2ffdbf0b5632322da477d2f04de3e233b735ed5a76dac10925ee7150aa98101bdb0959a578c20d416b6ce334651c3d8fb4ef638e78204188b70f9688b736bb1823093f19756e7f76aeaeb21145ccc36d79019c81379c53603d856e637b6f16b737f6cab3fb2273425f336337b9abe92fa310cd1c3b8366f03d09572373f3f10de1323ae7bb1fbe2505b8a1882cd6dbc87da58737fa38339df36ed438bafdcda205b228883975734d79a08eaf9360f2111e07bbf05c1b27d9c443f47af6c0754c58f6769c26730a5cd8123878a1ec223ccd32f9b9051f00081f003040a0d3689657e15bae0393545b1cf09547da38decefc3978033ecaba052e96e42fc9368c46bd8ac2a56e30edfc4caa5b6a7f451287a5d7450745ee58b08a2b04463096dd4ee0b12490d8c9ca11d2f8834e7899b4a43b011d1cd943ff626adbd4f8f0ccc5b82c9c258ee649672fb02c198e4c5e12439fe239341a69a23a367e66837ba6eca5d2f7d7f6b3e6451ebf70291466e92453300c39789b410318b6e824d4811aecfcaa214b544847b3dafee9de8e701644d0677f40ee95e37b5700119f6b0c508b7e43db056de7824480c72a83e16aae561f58ed7da1b72ff938abe2b29c857eb6c39b72d4b0f16f00ef2b8d578a69499538161b02ce62c746c4848ef365c49a4f7ccd6b9a837bd1827db3e7b5fa42d715d32b2e0e0415149ef689353766a4bc6514bb7b3325ce9d8a108e1f9aaf754565d63b61cf3f912dc93a2908f739bc6b9c1f80855dd357c0d180bf6ae07f8499bb709ded02ed419c49c1b02797eba3b647dba7727e797947867a693a0084a5dbeddcd253214aa8f4f94ac490511deb0f7ee2acbd970b7895c0eeb803542d66598948940895203bd9be19d6683fb41dcf5afd803da449d89137d2e0aaf2fac71d6679b7ae347d654addc3e2e96c74e5b5166495a94fdaba2fcef03a6e8b9a2901b875cd508db1d4bead9aac1b4888c8712c3137a5d27a09ab2808e508e002eced881f2a9f6813b81df60d2b72f21fa58569ef254b7b66094e7a8be04558826a0d3fc14dfb49b84c6acea82928c3d192a7a853b271724f62b8d491b3333ab21669c39f4b0367b18506116f54afd686ea7fa2e7543b9d7728cf3845cc93f39ee6345fe7e4eb763a9cb24720fabea1930c21d5290793c44e6ad80aa62662750146e303304da6e89241841f8870c005382d12e4537d50c09c2611888ac92b7a1cbf3c133680a8d62b68a41a582d366ad17da472dbb0d1beab5452dbd56a8a89c476f91d45a80362ae7e019cac63879ea01685ef4427aa358162721917d4806dba1191902e23825ea5841bd06c2a43068f29412992cbea7e88f49d0b34ff14ec89acf1774e98a3be024d5479688a8bdec09e268084e16b0e46d145db01473a723d08f149b0c50bd38848f0b8c6fb04f827d1c86b7e9c60090705ea8d00c8f2370e16ad570fd499260f4edab6e784075b96434831a9e602428b0d13c352b8f9870345a87f23bdd03170d44c13a5f8918a4f80db3325423e3a0c0e4cd1692919aee1fd31c95d1ce83fe8c92c3a2c48022b01abf19470dc16636f9f05bfe444754ee8881cf315ebfcc2bff68c506a0a2d001556994be449be32483bb723f4ba5404c041c6e4c30ac086b24e991ad439ae258eeacf61b47d01920d407e3be5e8c7e49faece652288ceaa27f3c91b34db2e1372a288c6af981dd7feb4ff7e0115a4bd4b16c7582f895047766c5020e767585efb855f7f74ef70fba6a9b587809e1d9a153e94bd5c1a3a03d7d8f2c0954be2bf9ad6f4571f5ac978e25a05ae04632d005071535ba6361868b3e7edf161c32841d82d1eea60b9e3ddb8a59fa2c4a98d891e6b708bb02ee2c92080194ba062422cceb95376618bff595af8c6007f65eb7b23cfeeb853e0beb6296cfaf5c335bde37fb10c5980b10bd8d18d7ae543bcd384e54a90edc013caa72ca7924ab93863f81f7a163643e367b651a81cabdcfa8ac8737c5742dde8e32b83c1ad521f2b9547e6a76c43aa4c6eb70beb0206a22865aa080e742524b3a38854b4446e3a642bdfe4a39d0663aacd37c440360889691dd970418478a7c3ca7a7663e5102e9364e20cadd84cb3c5edf8fef269dfe2eaa84d7b5a682f384b360d13dadc3e00b5201183a480f3360844dcc3ec5816193d051992349c52623a6105464b045316708dc84ecd3ed5497f1c5777f35722b0ccc8ce0cde377b3b78164f06b5f22509486a2da2f1cc530d02c446e58bbdd92a399870e9eb6f8af37c85ef7b86ebe3be3daf06ea01832e8dd56c1a6fbe1c4a00337ca086c0270955a016462f357c1909cc885fbb73dbe43dbe2b88fa55e1259bac2acbad029132675f080ddb4d0f8c99241e2a85a1c1e6b332cc079d30185433bf290802a4b5f1180700d4ebe6050854a40d055a9268907546de4a4ca5df42413e289d7a8a9d0d728c8c4ec487127c1b7a8bfa88c6e6540db098f5fab04804ef6dd2988f949443002b9a617b55e436f4f1925019416675f58f2b7bd410fd94ad643ba2e882e7e98bff020463c899be9141db096910d4c17f16ad810b8fbfb4c767c0c0e3a7ddfa8b19f800e9bb7f5a940279bb5abe20b340dae8c0e0849b710e4237d365f2b880988e289bc36080cf1631f428dcfe7c58c607777e3322e704fad27285e4ab12c6e253f3448b50f34e85c5d7202251e8c19b458893e6a221494b8b66baba43c681e4fdc51505ca385751f5451464bf35139500be6002181ebad7e10798a530049df07fa538863f6ae0d0003d00aeb278ac62fcc1063bbbd12729dce4ecc975db6f967a5ed5b251ceb73d0ba755325c5d967a9699cb3836657c6e65443e0babf27406de68b633018b6a11d3b1a45096a94697e4ceac8b7f50e75b6a799d9cd80b0cc41aab91d57da4e39d874326d90e0b2096bd9355f14b01ab1988ec1b38d4451dca8f3bb5c27590e68a78afd567ef8d3c3cbbe406db658fc7f93d5f1388cb90ecd9d36c6ab59f69127246529401b409416d98bf0d87d2281563018950c02ff74688ad7fd4ab6589ffce8a8bf4889d549e41b84387a9cdfc83e183ddd288acf9a910d52e26c5a80fb1e32eeae3eee3f36ea17c97041a4967c207fa71485e9f4ef7183f8b2dca81be15473fdd183eb19106a1cf1da4a105e8f7bb44eb04e11a1704ba969f3cd8dc1a0005574201712a1f9d7411fc37e3f82a19645b2102c33799d1412381cb32c08ae870e269c727b403fc848790ba9d5f0fe5ba1d740972a4e721a0516862a6bb0e88bffae8bb1dc4add50e66e8393780e90a96698b4b395e70c2d9f400ee9b106d727d03811c5bfd96c1a120f15fe6b81e0d9f9cbe2935bdc792aff6be1627854ad1b2243775af5efda79b1c0ce66e84bc933d310c1688e45f53bbcd7a41d2b304a6ac77ab3c44a4a0b59745eabb1ecd40b16f1e1c993ae5dbbbbd6f1cac666e144df2c56ab845f0e8bf50b3bee552c5c98e7f45910ca53caa342b2052b07257d3b622d182330844a5bb12ae58928990fa8c70d60034d4af80b91f921ada2f9bd5efe34591e047eeb58d2301300739473111b89f483fa514a3f5fdbda707c6ff604c44e0e1d9bbe372e035aec6ff81892726557e5206fc54b0790710392ce07c32d5ccc5e221db316bc200d99f6a7483868e6c31495dfb80657b83df83df8bde52b134ff628888a0f57edb858d36aad5bbb7ccc651cdaf0b2d1956b2b439799238b01e7102612c7b64d80a7ac64484771422c4c64b6fd3feb28f61adc7b5252dbfcd6ba62c20a533cc13ee3ca80fbfc5a8bd5d6b3e771a7520a2e89fcacd3750aa3dcab523821dcb58afc40a6df36d08a6c03bcba228d7fd2c7bc166ee624c9a3b275e624506fc3c5fab8f37493282308a324621de2ded247692e430c7a597555995d1de8cd62c2b1210266c533a2d2aa09c36560a62ebb68c34f395a6ecd140349bb1f3bb7b1cd8a5bf72d84a38391bc54407792a835860dcc2f658702a360c4fd2fd28a8c7fe0cd57483c6eca8895e0fa9f5490081e314154b425254cd1cd83d4071cdac3e80f76f8d20e229a78597bc2d0fa399d456854c0fdfe7f801312fd2b7b19a8c603d107c348ee08f0247f91d9e12742dfc48158bfd1a185c05856af7f8159695dd7cc655d9b5dcb58565a1eb22fc2b5e5a9f231abbfe1db450d06af7dc412860cc6c005c5e48efb6bfbbdabc06ed6f0f93918c4e1b808facf62afe55a3a655a351116781dec7973c31cf7be62d7be11ba5f8899924e0873b5a2d8f5b6b6e2af28ab4ccb14c4a8f489cb3b6dc59943a336957ff5a54cf31c5982f2816532b810dceb6c35ec0b0e52a3f22ff54b85a46eefa4414386612465297a75aeaed4f7dbc9c5d55b8b9dcecc2b6ed5877fa33d60686e55bfab189c60f6561214e7bb57b27eb4818f0ad4a10539054d673a5283912f6be9c64ad62b34fa685bfe264d8d3efc1b13c7a4928a11b023cfb33a1d52bfa7450f6fb8cb70d0c2d9953fe8af34e78d2275167ce0ab3df873b8ff35aef47329fcff836330d96b3e747dd2c0932ca4fe65499e8c13a5d3fbc220533c7045207100d8f293627ac930d77223239780614058975e307f49227e8a748c7ca9ef198f29ce1c849a7e95c501d8794d132b7161480ee9895b5e02943ecbed994b541c63de79a965f7f7615d1bd24738991c1030af02999529aa724aef2dec33f24f271f31d5f811c723ba4bfed3e276441786d9f6ff8d49bfd7afb58c77442ca012ffb2cca0f0c69a981993d0c5ce8f9e17cc85c4c7d7d5fcbff4e04c98b86d99a08b1bb3827d8aac0750287de5f7f9abff86f90367fc5c115daaf9ae22e6085af32fe5b8c2eb01267cbc6d8799487c2c3a93d5482163f1b02f89dae8b93fcf518ae2682e0e13bf7d034a64fcc6b4d9929b766f735ad35aa67debc96ddf66624f319e7332108c587aa9e950b930084adb937adc9681a6bd382a438f9cb4deb45e9b7e13c1b734f8d750c2b581e668718af0b4576fa7e9ac26442a813dd304593bd070324511b8990deac95dd41a77a9dbc024ef9948c3a8e15aac5812d8569cba0cceda9340bc243393d6a060d6f94e2061dc21055222badb529c4f45c96ef8dc2504ac8d44038d4431d3377abc6eaa952f4029304e72ee6c40e1e7d0f5aedfee6017558661aaae1854c3b05840011839a1ae608f27565204254e8d3eec68d4910e70797af1b400244b4b1bf647fc336b46c0cf18b6ab39eb1220a4edcbcd32a0a911c51bdb7cb2deb6f282e3933f64f3281caa75a70379e0105d2b0405aa7244be4b008271d03133cb02c3a7639efc1a305ff1ce539a066f8d16a16dcbaa7d17b0c38342774b4d7f854bed14b7ccf784d00cbcc2622d4e41df94f13cb4bebf002e09564b7e98e6f368fef6be5bd06d9ed79bf17c509645fec5a06393383ad97ec8a662430837dbc09d0d5b18c3d41d641be8c0a71665c36e8c4a0d3b20049205a7d21e8f8d1c255c3c100f98997b72102ea259bb878bd0c99b362f1299555c3e7a71f2b5284292ca24d314d797d78eb97e36a7af41f8b8206ba4c25eca2f7d045972cf560c601642a6619bcf817141a250ba34cb1c82695b0c6902c55408c63384cbeb902023673cfe3853bbf188a6b566ff1c1afdca067af55f3616d8b583e12b2bef01c371ea42519c2678197e3b5485c17b286b7a64018d8924624a1fa110931e67e271086b74da1088a0e9cbaa5e8bf688c4bb3492d58253d8e60ca1734b04ee23815fc457fc452c903884a96dc9a08b0127a79c4859631869b1887ffa1b68402581831a59c62b051bb29bb7903140868d2a1931be25bdb21401f9be29dc8348bdf045789de23733eac98fad0c00cc76adedc33e6acf22541eaf73cae52885accf4c74a47da039f7d359e5be04b14828dd9b4a755032e3e1062b43c479620d3035bfd83a998c1d47162a8fc0c09f55a42dca4672118f3883150b935061b1d31a0cdae17b0027080fe723066b4da6a87244a0214861157671f386a984a482c3010827bf2442e0af84cd0d529c32ce329974b0ec08fb4267bdbe56b7a59430a7c3bfd167f5673d93945950c930573ac1fc3bca813331238b20bcdf38f5b4de7fbc5419f1aff2afee82ec6387e154fd210104385f33bf8e76c7f8ce98e1e84dd7900b66c533e58a6b6180e7d3759a414bb442006e054b53734016710a35f42e2cdcf0ebc80a96aabbfdb23cc1529190a3de55ef33e6cc646307605246557559a2246009449831b64b3aebcf4def40244131e8b43b2956a4b3c62d69f423dc1e06282e196b8c2ace7e708c6aa7deb6b2a390ac7de5fc83bc08c894bd74857b3266fbf18d115a6349119aac2bb5c6b15a62636542a671a7f49a35181783f7f1cac04f9f5dc2ace6957733d100452cb237b5a3e11247de12bbe509346db6caa2bac805d6ae34ff24bec14dacf3500a4a17269a6b270c8e5be7763df9ee39772f7ba23a9aeb89fbfbfd5866caa2ebb8bda6932bb6bf035549f814aa7106a639fa07045e34720718d18d1b5666cdf3a0b607c2b8bad3c397c01f898f788212981f67f4b0758e0cbe8e18deecfbdd5606da8cd1c8fc6005a92777f4c4cd0d678dc2796bac1c06ac279684fed156129bc283be4a886d42b8a104a90c1aa16f8a148c69fbdb5d4aab048837524526fc491b44b9e74211a637454882d680d3022b8c035320b724e97c6f8b840244e49b9fe587fef5370beee82c6e246c55960cd5000510989384658621fd6d75fd706f92da26a4cbc08d0272ee9a1263995f0ccfd3a5f40f569cd5bca8ba990a34ed1937099449082dd2362ff4442fa7b40af1163e5894fb604243691aee6f3720fbcc2af6b7b9423428f6215326bdc49f7c8f974d3720e50c86b4ad14437a99f75142dbc1b0f711cb23bdc60173e406b44b92c6ffb14cbc29718d5c1b0d218d0c60718ef4830faab823232d73d002aa3dc3a09c8aaf097b60f006fb296f0b4d70b99c0976285fa0b3dff9d9382f09fe87c58c025da27bc0974b31b8edf911e67da6a8d6b68b6bd6e30cd047e581aee6901cca3fc822073ab549d56cee26ed9d88a0984d5ea4bb7cd192cf7d1e441f4ccbf854bd70161400852c305aaecc733420da2f448fb72ea15d5e45465f49e6b0aad453b2bd1c3c2f542e65910ddc3e65910199992efe67844324c97e17c6bd2e7aab8264477a06343b450420623a48e67b9dc15d779ee14898b75eb5dc75081b488241849e537dd880ee5fb67c08b1a53033eefbc2c888a403d3793ce3d40a778a649fbc44ef498a526ed69a27dbc5f62926c1fd1db08fb262ff79eee91cc449fd6c682ad6d97d4ad11cd0bef5125ffa4669d19c29849aa4ccf1fdf4701d4897f3562488db7f2ce548d0a246a168acb09acf01f8655727f627b576a3095bcc7e20b9bec962ca3238aece450645858453e18c67212c86c8bc0f160ec6ca5625613980c1894ffcbe53d90764b9a9148c3b7c19c07206a649347faa03a7a52c62c6175cf4b771128991e683dddc4b07c47e1a4d663bf458a7ece86afd6394aeb093e38715e9e1f0295d1d6cfdf03eb01aef7f37e143d207501a2065d3940101d5002d5deff6fe338e5f7061af70ce29111f0b5e95fe75ad346fcd0bab758538280b31513926589c4150ed9a1fc349f287e1c30b9b4abc1f9eabeba3b14d45e7f248cd3d2d31bcbcc1ef6e99b9f8253d24233b741798fb3563c6ed60679fa691dfe3a969119e743b39d3949ffd9861621a8db40f2d6226dce9a6ea3ccbe4ed8be1c2dd19f50d91f1b220d8068fc58e23e6cfe33522b444be095c9fd2e2e1ab005838d3a6ddf5fce7ed5af4aab758365a0c382a5a2b1f56128890a94cc466e48d85d4ac47b8155370dac97b64e2f69851f1bf2fb2b579e8766505dd631c96f16ba6130266d59932e832b7c054737e230561465a7870029de11df4775961a303f564763933ed0283793aa97d1432bad37e4111e80981308306f7a3218a7d1b0cb5e3d4f1dcfd97d80a9dcf8874d00999b435f3f095ddaf17b1628364d97fbf7864769645d16a7cdeb70141562d8c63b89be201f3fcebd0f742a98c9bca96bc7de1d9c783e95d42d0d7a4be2c6908cb7f7692b33f5ad88a2181eaee62e6a3c799e0ddb1ad62e9d0c583ab3126caf814648e101240de3c580f6680e6cda1689e65fba38d9f7fae640038bb68f57a33be1e1578eaa0456f674efba48fecc64bbc552b199a60a9b670dc519a78ae5002ab6396db98bdd4d440458f2b75f2a698f2b9535705754cab0e3e427699ae912421d1b0bdea2740a6b31c41c3fc93a42fba1821fb9e337d6c637e7dd6fb4773829aa9d955e47787a80f818ccca9096a6f39e46634d9e24364122be7420fc43f00f0d47231cc7064a880723886eb2ffd8e3043a2f3a2b8241336693c12c8eff077260a7c5dedec5a938b74fe33bf7b748c5c53e712097531ccc25bdcdf5329e3902c5349c1adef51eb3a67933099efd029c143528dea72a1603a8559964e3eceaa31bba7b614f238af36956d7afc3af240665996e528a5b06f2a3f9a219429d01216691bc8065f6e48c01c8999198961d8772f71a616fcda9c874c9cdb0385aba2194524302df51b3d135a324b51f6a6068576e449dcea0a913ae5bf33e3e06c63d6b34b253a3b6f41456977d215b30d2e86126e24c160abbd43501054621e6d4e133f6ebaf6b418199cf4d95850acc15fc1fee1bdd2fc5e51a65aa0aa9da955282c9bf98d4674d360d34872cfd552000f6990aaa2c2c76eb46ec02c0dfc7924b7eafcd54dd7e39cfda4559007dd3f467cb30d1e94b8eaf9672aacd9a95a2f32214f8bcb13c23bacca1be3fa1e31b0700ad91fd6505fc135ae59dfb215e79dc9d8226066e6cd05cb843c2e8df5cc993dea9964c29655b2be5f11d7d5766255b2eb6ab4ae0fb91ffb8d3ab05e032bbd6d2387229e80d49502e1ded2787991d31c6bff4bde0e2a6ae2d10f742b40764d0b14bfb59e3d992fcacead16e48400fbee1274f7614d713f22656621148a0d296ac57eea0837a377851cafadecc533edce9e06f96a118715f92346bcb050fbf19839f8119560c2e4ccf6a6da331267d338d87ca97dfbe72c1fc9d172f23dea0e20f01c09743b1a080405c283b7b6270af794b1ed3fbefb9a061fff6a84f8180cae53b3aace79f3a0ca3c020efea8ddd5feb3f47e846164540f2aa8d4b518382d7f3e2d77ee6a0826d612f1e6ce2d3f735eacd69550f46f1cc7681504d26bb9bc744b01153aef33adb38604e0883c3ad2f8a7579d7eb13983fadd3344372d7bb2585d84fbfe7867ae4fea442c67d78da43422e8de65045d86e8efeca44360e1e52029a4dae331c1c8e1aa0dcbb08087c483db58b85ee3764b40f36125d0607dbba00c5b8be5d59c391b582736defce5d8ddc16b49013817bf55853664a71b8dbc4f45e668f6cefbf91c45c1b00528ccecc0b5c7e62257738e1a95bda98d0b33793f93f1159c44cb07721d11fb231932340f63d9d6eb2dd7d179aba83b7909bde2651d81ff21ef25f6b6c011b31a303981ed2e61f566ebfcb1d3717dc33ac3f16e6273bf8b2539d72ddcf9ff1a92af524fb96ec4b463d3a03d268e081cd7eee7195c451bfc10289fc0edbf7850f88aab1c7a2753a95190834d7df9b983540199dcb2045e99de8a498f2180e17e881bd4af77f3ed24fea427033bbc8db51b6edef72990562be2e8fe321a5077f7c97ef83120fc0e02d144b9d211e949b9156a6462294cc3c8135ea9fb45697dd323759322ecc09898b07d00e7536562c05c2354fe5d106fc0529ff943a040bb4294e288feb1f431be2bf70abccf57fedc6ed44effda2dcb2f7bbed3408eb2ad79f58900f9ae5077111d73cae678184c655ade7961e0b0aed88334ffbe24bb035fe7878c5733f33aa02abb34e563c40e1900ff393afe40fc5a27806cff3cb9320de189edd57a643b500c9177c240951f60b2fca16c05c00106ddfe5f4f5981002e4ba2e4e807e05d683422128c58b0f0003515b563a72455670d56bd5452027dd18541014883b2bc75cd9685f92082e2ed9177aeba11701ca577642cadd7025b19b8531a0e8601fbc75da92b8c4a10ce8cc91e26769f48e5dafcb63b0592e154e0d899cd8cdbf73e5150d8a03343ab8cfa4540624d991bb455750e82ded2aa8fd94ffae05b04030ad1039adab4bf23e7962797d1ae2e11bf4a744e59335c6339e9e69fb296b8ef6c3f4ec879ea64f562e0889f6a30dcc72f6c5066ee403bf556a6e94198e6e20a1ded171e6b7328b5f4ebae896ec417a7a6cd7ef22f436ef40f5d375f1fdd0ed7769bbc77d505bde6090e628549ab5cdbfd15e9a725654b071ddaf1dc7bcc897a447ef3d0a90b41a82eb78089b372683ad36f9892b55dd2b5fb6d087125563bbb94665ccf50115c67775d33858e6d6b4e70dcee8d41259f908b77e91d2197c6e272c6be9ccc26e17335eab92cad1f9f4156703729b6ef6b5ab703a50b58ebd88dd9352e5846ed5cf3fe314beac25b2f89b8ad7e765f6b6e830a2753f825e86c72736c113439f1e37c150e0ab2b4b6985efdec9e52351e17d4655b6020618dc660869976bded0ae211421b68940367a4e18e7c9f08c77a7f102886b16d299dcb349938dd84450e61167c5a529e50fe1942e88b304191f68aa444ae2ee07a2eed0f88b62f86ba386864cd782b65ae056c9bdf7538cfe58696e350ca1e4e75b0621dd6f64f652d4aab4f6e607d73f0a998c1ae0eb9bf3ba3c6b98a04079289ebcf80e6fcb40bda9a9290714d3b43004d54ec310e54797589243631e4c7442b052cf6f606c0a829cc1756abf66dda3469cdc9f9e843bb343481a866204556e4e0b472a04134302c72e8532d220c6678af60f80017da35ccfc52de440be856107464ddc0d5d995007b878e3a0ded9fe8853c9e96862aee465615a7a3ebf01fc92cccbd56332a5d3f600a76e628e119e3b271eefaa83d98ee298c617727d70086e4b933062dea00a97b02011aebe3170394d9889e7761d03a604f51022ca33a158a9ce142fc9b6dcaea9e1b2276d4b9460502b252eaaa33f088909cc0d6fe2595b33c57316f9e600b0df6dd0bf809cb8879a7221c8324e29a133b03c9a4525b74ecc653b4dec72544aa7e7ed08db66403a4bc8afb7dad136df164dc0853a33911c82797e44e2bc0641c210d465af4215c1b9cdc549ab06491fee1c51dde6dc9a78c0852360c09e573c0d42045520826a34308e27866d8e42dc573c64be37e8156623993298b55d45163034024548731491d7be9fce5f6ef6cc7cb59329dabd583556b484bcb74c82526f7fede86f83a2961864cdc49e5c321a94570681c353dc2efb3b49f7053756260af366ef4d88db9e63093e903724fbe3ed7938e265035ed8ca4b2a20714a0662df25c468853c5be42b38198ced8b9b058d1717f3b8484d93f785f420fb13587116e44961c87e30040ec6dca82aeae81c5df1214124fc5889e243bdc93a0db160114324c8f57eaf42875b6cdcd53c52a65c4d56bf3eec9cc6104cf5d7e6b4b9810e9dc20d73e9d5e815fe78c2847428feb60a33d42564234096aed9df6f0d304f116382aee8199d1101084ef75fb87692fe7c836978888ada63d52ce42be8272b0eab28b1514f8252f7aa4961a7307aae192fbe11e571edcf100a550ad1c178a4968832a4abacd2e9b451f2a236747a5f914dbfa14e4629ff9b98e4f59b6c888d5bd152ac6a27a25e537057cc9027fefba4b12542bf318c66bd77d7280baccf75f57f42baa784e004763d51949061caa3e957c2b3d03dda3f9253335e09cf894392447570047b2613bab75f90460756438e197e51060d4061621f14a9174b1902f34bebd51b5c3478edf66eae9b11080abf801d8aee5f24e78eb3fd4a8f8b4e1fdf9645e017379d4eb128caa23485a00d94dd758ac40ab891a1b2c40c98eb9ef6f67ad8c73846c5d5341fcf5188c2ed5f5434ac99396d35def886a7db2e24774dcce9d395046877d3b18fcbbba5ee31e48ac9a4914da7c876344044524f8514d51dd661ae0c1f0596020cb05f90af2c477b26493a917ba89c238e7475e71924f8ae46f4cfd80ce42cc21204df48e4fe8fa36c24e41c03c10d69c64e334a444cc70b54318dc9727d45039d6b7c070d1549a7e828fad3ff5badaef9133f794cfcbc92b1b6c2d04bc3f79f015a1fd65c7018da067112a0026341c6eab2cd0e95ddf6c8728dec13107f461100f55fcf7e43361dba2ff21dd89bd348b54118754059f76356c3f3b0a2d690d203a1176c5083b9fdb67fd1154d34881ecc437cb5952b6341e56e1240257233a494ba983045e2bf140a4bcd42567857263bdc77001885c1e90eb0c2a18c886b191142e2e53d574a555b576572e715bb545fac0d30b8ed5bebdae86e43d5e4744960269fe583b120ea142eddb61a479c6b7b7e72fd946b4ce8675cdf4d8813e5dcc0406c6e3f3cad17cdf590bb2bc13e5f227ec8dda0b7d4478d4e3e85021343fc206e8e2c2caa242506c31ff09e8b00abdd5147243b595f59a743619bbd515a4929a3edbfb000555791d83fc634c7e1349709705ba5cc3d8247d54787958928720488fe0f338acc86f3a3eb761fd5607aede30c8d188d604df761b57d689b9b971645fb806eda8b96d0df7e530f414be2726e412790fabc4368d6db9f31480487635584c8d4eebdc42657ff842e698bc0637e8c507c86c94c591b56498ba29fb29990a76382171072c789d88036766adbd3a5f4a36b65b078c30ff35d5caecb7f9ef9f3413d20dd00c3013607c020ec7ca185c56cf248a88c5c622daa90bb7bd877e918df6dae020376308b981781e2bf9c0c57e48d977ee6d0e3597033ad48d5afa6b3c56ae8ca24506ead0a5a58e60193a7d75de612a83265a02b16662c6541004e33d82036785ba211475e5403bfe1a79d732d9b33f0470a94e076bf25fdff9576d158142a4002a249d15b20b64503b1ddfeb27e0fec1e74154699e73391d0702131908e7a8dcde0d4d75808d1f94b8a91211edb879ad34377676f8d768a211e63800eefcf56d654273b3b1e6a1e68c3fc690fe1aeb92ee4578723f74b2226528cb24c21af8c54abef6add900cd07fdb730153af0cdd9be1d03130270c5a5e5f0ecf491cff27809a1a5bff7efebc0a6f0b610c1718fab0234671cebdd72db11f76afc716962effae4663ccaf500c32d9c65c131830ad4633d10af381643901d319530419049a05f68dbf892beb49bd3e091a21a82512ccddf4af522f31375bcfeb4c87baa3ee2d1c73abec3c3b5f5ce41a01e8bd40037ebbeb0c272cad4fdef5ab271b7608d89494eae851b95ba0e91c69cfef2300ee8d5219c1a9e21659f2800727bc4769078f3acf440af28584173b39092614394c8e0dcfe3701db272216d64d4beacdb7748828d73a5712e12dae85081d089053af84f1c69719a4aa26013598c2c81cc4616b834a3cafd9ac3b2bf429dc378a0064a8a32a2f38b02f5f984c8de70ffe2fc4c5980aa396f07a92fcc9e4c96e8bb7f645220398dcfb998f74c2ddc8c0ab9cbe02cb31364ef8821e24904b0ecdd5e8df9c1f1566d7cdde38e1be6d909104397371ca4359be58cb45d6c45390cedf4f25fc99ca7872ce030c15efe4786fba0450566b70692b4a8f5e4549c2b87e6ae59006e4d1fc040f86579164f50677b48e11e5508812b9094951d6e89aea538e5c183dee99a120142b54e8f39d7b4304d735d52dfe33ba63a894eaa86343645fc0677e872c8eb4e7e84c3a1d05637bff309af9249704e0b39b11ba14b50a08c7ae03415166c7abda4f88942e586b640e7b97ed81a9aa8a1b721a9d3c1ea33eb681cf281fd612794eb6bde6a43ee031061779044799ca403d2bcda11c9fd4410336ddaed0d88f870dc321a824c2d4a702cdaca0ff05e801236345edc7b05889dbb1e463b2ba8831d03919632ec4e91f77232f980b71f99087bbe77bd21736eb993fe5cf147d161b4a9033b10eaf5f29ea0f6c0d6ecec0cbc1b90f02ab31c60e54b61e6e3107df4cffc33fe84802033f094f2c82c4c8bfbebca8e61074bd00565b98c41ef4a1778dfe7a2aae2ad02ccdc884c411fd77c46f199e85dcb13fe38003ae78868a520bc5518e1b5e2721584b97640e03bdc801c559f5867f134ab731ff7c945bd0c71f485df8ea769e08bd8878fc4121994dcbfbda72b823fd6961f188acaa4277a9afdf2767034780df7c5d17c1ac6005243c7a46cba8a0b9593ccd7167346ef98e9f3942742328b097a04f84c2f260da33071ec38afeebd0d8177739beb25159bb479c106c58a67bb26321bb8369387b24701e240646c3fe099b2450b8a0f1be479aaf6763f3ad153469348cb17ca180314549cd167dc56274b597414365e85ebbcda56708ce81c4f1a9d358167119c3760b3447b685eaea7471df76a2a8f033f94c626a30ed69de1e14d85ee1e26913492ddf2ab46c629cc021eff037e0f5d30701941c3074256fb1d8053b214d383166666440d4c4a44cc5d314f7641eca2095607104bb9636de625bc3d6593a210d9249222ea42b7197b456e584ffbc1e7ca4dd7820ca235ad5b4c6b9fb740522283fca5d8610661a86b71df3be7feaf6cd54bb4f8d2ff15c737fc6f14a36579162564874c696a4cb55e1f2d8639f7e715f3bc8126479916dfb7e91d1b88dbc1beccfa822ea5013972f0e55eea310b1f37ed170717de2958dc38fef4167108ce34e209844e91757cbfb7f9337058258d32f2e7493bad0894821086c01639a347d0d6a48476e3f30e8b292a5804d7a5164d28babd77e68d8dca6e40f64d32081b98af9bff8af90b3017ff92ef307a27e232d2e97f8a352f519881dc35db80511f2105d93762a9e4a9e88c5c58458606c2437db3e889931d490ec6472e114563f1ef41f86afb1949ad6131b9f376f3fed4e0ed25bd546aa30ce1ae24cc32e83728943df1cba03b8e00185a2784a1a8e492cb92261d82367e4106c6ac894557d906932cf4a60cb3a95365c6c4e396dc0297dbd6ce3fa08e6992675fc548c4302dfd513aa66cea2f7e2381fe4e2bf67c1dc6b8fc0444d443ea84658943a75815a8e0f620a4022aaa70be0c7fd7eee891d3e7c44b10edaacb44178c32c434744bba6722ebb355b4bfb0c69c015dc8a3151faa61ce4bb28e78a4737dce3b9bba195dcb6381f2b27eebe1b670b169f70bb37d7315ea83522f840a5e0729be15e4593f3ff4ebfad54007d4d2af171dde124da372a35a1e18fa50f187000e75176d69965841b815d5f6df616b119b85358f521409b004584dde63048cee6e841f5a9066da8eb0e7008bfcbcf69eb8ae5cf43bcc6975801a25b18e2f12f3df28e324be9fda75d3885a67695ccda31b6f7aa1461569d5043b2e9b59879d3fc03bd12cbac3ac20c3e0d80c633b6f396d0d06532167b8a8150c95eeb4b48389987fc73873033ca86439415593c328c24bf217b51b3891164b50272df9e4a47afdac1224893853fef078c3f9cce47696a3703a0f6fbd46e8c4c50d95610340cbe1e3af189457d30a66c7b3bbb31d06458cf8038dae71787808fa5e09bde644759fe4d8db2eff529301268e570f054ad9b53b613dcf24032152c43431118e7040a83745ab83173f0931bdb1a9f7b28c95b84293099868f15c7ad52c5b49b845323d54934c8e6431ea53b34d069edba3b8847a7a53386e1c0e164caa7960a8c4235bcb7c46272ca4eccfe42ac628173f6501b5a3854ee7410b2162091754624f1792d3a46b8ce8d7c4188cdf370095b2f959fc6b5b388dd17fec254a4b79c431d268adc19f0e4ec13432d928a3600b41749b5bddec11e4b449507440a14238336905760e4fb318a57ed118ee9abd92f246789adee78f046354bbcd3bcac46c7a6a93cdc9325addd5939ced6643744bce53d8943d72054039dbdccccd07073489191e1502ce969a7a0b9ed8abe86e15d765fe84239d1a4e1ca2e12894b913cf819f2a1146b2203b0e943b10d9472be918d60a5277e1222866e6eec8359914a07fd7fe4729c212d8f200d47a3090c1570aefb3471c3024f01321bde5660dbd703ead019206951880233dede1bd3609466b7a8971924a047bba865fa0fd5113663225e10321b50a6a431dead1edb11c9af52b76f7d407b35cfd8442513cbd40d7fc879a0b4fc50a9cb43215904f63e609510042063b525bc32b5fb0018860c8240acead25be6f812477dbdf1dece2061d7514b8f3e0d64bc7d70d6d7292769ee01be8b49a3bd9cc4661fc96e0469d252e999bd8da6091f3db27e827f3c92009aaf434d540b6d3719055214787fe9b85bcb8ad36e941639597cf2287b3f26b72cb246f93601f9c35de6a869708e6e68d5d8a009224c9b78117abd093efac9529a5f22bea6abc06e1351d3b0ab6c049053fa70977a3ad24739ee36a0acd6ed930764c4a453a159b470259261f7eff153c35ab66bea5c2fd556741850fbd0f49cd8aa8c37ed6a59567b08f523a523354a5b4a2545f3bd1c42488b7718a625343236175569cf5596d1df46464167e87f7e6d297642509221e4eda915db6adafabc20f426e7ebbd5be456c3408a6fa21ccbdee81cedcfe483971b445ea38ca9589cf9b3fa8f34ec6271f2871abac7c5093c5d8ef21cd69495c961ffe307d6f9e4e8d3d14b563e6341e399eae4d8f09cf39fab0c2b1eac8046d0bc4021d1d5fc8d221fee824916ed3d7d129928a4ca194403a80aad1a08a37d804a232d92acb43811efcfc2034530f81841a3202b4b8dc5c5981a6944b6c8b9680db0884b1971ebf406484bfc797974424c856648b980100b5f979dd90f82911e4f1ad42efe80ba9ba35a85a3f1460b713a1e60741096dc5e9d48e889f4384c9e1b218f0821d3e116bdc49f53ca8163b98f03dcef7f10406af65a1127aa576dbd9f73703ea174f5ada19dce912c1536cdfb94064964222ce98502275828c13480c1f512cf7a10d05a785e680a10549775797bc0ea2d7435b60bb8a0d78923106742aba56c37b662c6e0097f2d45996f3188f2d173f5e34f552e9e857ffafe1a5fd17e671d6bb0b1e72bed22e59fcb93c8728519f90589b3fb757299f6ae4ebcd6f30dc7c50ff1a95a949c43ddb23cca5c33d931b5459f280b6ac7126a0a4b2ceb6ca04d11829fb551526188d0dc5beda6676b0a33fa8de44c46881e653e4106c083e7c49aa3ab85f02c86a30267b79175028263fb893b3452b1d6e1e2ad801e8949e601389b3c533ec3d64b1980d74d5a35874c9e9265d8661340623cbb7b69d291f85e00ec0aaa0639e92c06a8a8f3f0550d38d5c5d83f9720ab066f8fadb6a30bd9e055e244fbb31ab8fdcdb3d47b4acd7da0687b52137f2f88b7e2e3e40117d2862ad7f3f053c784978e4e43cdce3dd51607f9664740fdb5d583211abe6437d241ebf9abaa90bd262ebdd9bd09416080b0c75df8d487e8ebb6484cb56d09415a397ce116548a35a9299a6579662d7a1615f889eccff5770401b556b9cfa3b4f566cd3a0a0890afb9d6961f2721a82c0db48a1e3513c01c9246ceb8fd1d3be7d70a0ee5efa2613000e8e640b7a2155a3dd6f619587bb7219dd58f38d1b7a6c8bacd05385d04fcdab867a0639a65c9e4bf1176093f39d033403d0546a8ed093c1f8d4af22a22f1cda54e941053e844826a84c052347bf92f5dfea13518a9b110c9460028248a0fd3acb63944ce17873d13274cde3f2deca8c666f03390dd2b6d6814cb227c2cc13118e7b93ca1566d3876adcf13fc220358774efebf0d527b13040f6feca58db538be6968065ab820a3004a06d7368a54ad47429fcc27ace1d3c82af32b283e3b4b619bb94753c808220772c7a980ac771adb0ac08c3e4d291f29542d2a2b7098d12b6304c9c5812126b9490ec1686ad0987dc37b36e9ab0ab451e013b2aa53958a074ba6cd73b44962ce507211295cf350d9c8e6fb15e2038966d012e5e62b4e6da7114ae9ac1f446aa4c2b5659ce756c149046347241868117d4435e9a13220245824d271ae27c63ed0d96d6b62453fc753b0a3ae6eb9ae07a63e265cb9065bad577dab61f6606f3dc2c168800518a9e84968ac11de829cd6236962daa064b590a2ee58fedc03d4c4fcd8c0a6839c96643e4ac9f563f6742ce2d9efcd8b28c3a3d9952feaef18292c0e561bc443ce02770af04152dc2d97a621ad454de71958632bafc1f96772f6464c923b441a68a99d86fa9d627c3422ca80c4d6972487d7e55b87c61bae625ed397b8d4dab7f616a9781bcddd0d9706defe66f61280ff512357eb574c874e8c15f7a92496daaaa0e9eb7f8258438291bc3309f93c9c6f2b34feceb50eac73046ea28443104d10104e6b3a7a8d2ee0c240933223525effb096cb125f4de6fd5a8615e1de8e013af95b2aacc9eee24b3722db5af4cbda61636314aaa3c2159d6b5bcfe7b8c8489bb9bad64840443711b2f7de328d117011430ebb639ede1e94b5d6c85a5b64ad253a40c40324eaad7eef95c7f4387e8100a902f2030815101f40a680f4002205240a081410205555553faaa8aa7c544d55f5a892aa8aaa82aa02f2a3eac78f1f543f7cfc98fad1e387d48fa81f503f80505551fda0a2a2f2413545d5834a8a2a8a0a8a0a888f2a1f3f7c50f9f0e163ca470f1f523ea27c40f900325535f5638a6acac7d4d4548f29a9a9a829a829203daa7afce841d5c3478fa91e3d7a48f588ea01d503885495d40f292a291f5253523da4a4a4a2a4a0a480445545fd88a28af2113515d5234a2a2a2a0a2a0a085415d40f282a281f5053503da0a4a0a2a0a0a0c2a07217e0aa3e75604f6683b5168abdc98a2caa814fc055c55bf384d85b49e75175d5bd777add05543dbe49ca02dd639f7af7e35f7f353fcee977be021fd7d59c0bcbe8ddb9dd790ea8778f5b6b2fb0b732ef72dab7de5d6bed90b55687b558d69638a4ac2d495268ad25c0e235bd60afdbdb88eed5fbaa7edd9252f06ea1bae2703d5d395ea27b14a7e9e5596b07b0d60a60edcd5a7bc35a6bc35a2b0404654e99514e1cf7615ff5f02611c9580b64adc8c65a9bb3b7510ccbb78951cdc36894e9c38472aaf2c1774fbd2828281f5177b79392427f44f5a89c743639ee77f3cce1ee3cfbf07ed2be7d793458b1066b6dd0dec6277c9b42f6363660ad25c0de46256b519ce6db7b8efb70f7dfd33fdc1a88813aec4dd463adcd616fe21ddbc0c65ccd37d1cd0ed6da02ec4ddc62ad5571fa421eef1e4d57fde3bff43c2b7d1846d5fc184c03f1ce05af905b6b80b576db9b38c4c36039e1dd38eccdf2b1d60a75dadb184cffb067de0d5e9ecd5a5ba3596bdfda7168ad55edcda2d6f26d76608fa7d3f43e0df7ddbb9c3ef1367fdae5b4f3753fed72faa466e1bea8134e83c1eb3445f5e3ed7b9d70babb9d3f7135df1f2fd5ba3f05d7ced76d3ee17dd114afc0cb9ff8c69cc993b57626c3afde47b1bad78ef7ab3af7db550cc4691098bfe7bbfbfbd4dcfb056eddfb6bad2dadb541d66ebdb7112021d00059366262a7258186100302dad1a5852cb3273f183910c92c37a259f9e473408a33f16be48f0d36e8607cf23b10bffc0dca74d1fee737a052e5af641d6c00c25fc99036e8c08624fb195207b3af2155f92bf269481dcc90a6dc409a72837cfb1dcc44150c0c5a76c4e0c2ad66c3dcbe645f8aa79521bd07a41801901d8fa0c8f6f8fd768e487bfc5e3b47a4f1c7720d907d5114c5f16b44fafa797bcea090a7edcbb22c7f9e562b693f2569a389e24f0de98b2ccbb224c9d24bdc601dc96b8353b4475086ce2ff13738654850368032748ae34f0dc9abf6e417d217ed9f4643b2657944da3540f68148afda93168b94ed5efe0c09ac3c8232242241d900ca1012f1acbd1532820465839386f455fef83212499c3d293e7964a190a7d6ed1c9f08a01fba5d01011d8d0f344a19ff8a142b818046a0d102cd908824b0fdcf17981fa4e1f8606c4843f2c5232954ca17235ffc71fc5168abf6e511150e7e5eac792026267604820357e4db8ea85411afc807c181abf24929475482beca5ffd3ced888a18f92212952937a4906f1323df86440548ec480a2941d7d6683baa225611af6aff7344050407ae6c5f45bcfa79a0f108e84b212209ca07fa2ef245240e7ea8d0be0684c4c17340bb914bb2d98b573bc4cc90bec00cc707332c5f142292a0fcf1bb487b05240a6d8de36c0ad00e2eb7f181866e646dc716d95b1e32214096c78e2be55b1e25eea84a15276579144249a54a89044208e5f8a290b803ba1a8f4000a1a452058412a90c14074ef24527407614228c105e84e842bee501448abfc45e51f9a0567bf268070a372f51e87f5eacfdd89f97bd28f43fca482f0fbc9a04bded9b20118d6fb3f1b60e7eae886a6febfa41221a918e886f036903d99510d2940eaea0886a48477e88b682ae80903820dafa19422ec9e458363219d1d60665baecdb5e7c7b760d7065439ad2c1958e5c92d951a9cbbe8814247f469234ac1d586eb247a11c6767944c3cb8b2bf84467bd98e136ee457f92b51ece0e7caf6a258ca9ad49ad8b721111de9e0e7aa4a19280e9ce27b202626ab217160bb7eae2cd2eda82cbb06b8dac07665919e5c2a4725997daf3f4b99ecc91f6932f20ba01d2580b14f8a625922799132d2cb7696a598bd736585489975f073354312fbb912c991b4ff800937f1a7dc181b1beb7355135f44223af2a7f834a4f4e704131f094cedad1552e5af6ae26f012282c08373bd9dfd76da7f6014133ba19063377690701bc77126be288ae4189f3ea307a7ec8818c1d3c8b1312844476e374ef147b1d978d4e7b4e56c1cc9b1dbf825398a53b011d99c160848b4a22d499b6aef9cb2b7efe5c119fc396548eacf58073f1e11cfaff4e72c91d49f130a39f65ea28d14af0011d97f40143b3d3867b32f47aa08642f4211fb9c2352fa73fe9c32b13cb2a73893d1c41189e848ed143ffd39c1a0d04ef145b153f665df9e50c43ea79d3d8035c0693b2d52fa734211c7be768a42118852644860806a6f796c0952b3e50c7a9b073535744eaf213564ce21d9d78cd8190ea873f635f194bdbe7dd95ee865415f0a61d93ee86fbc8d07fa598914343bca713474e4259e446fd46439818e8880841ee8c6d70c094c3c694f7b203188f637443b96366633a4b74ee50389378e68b5b3f644e4db376ab09c42477654127aa02328e5598b00e9ebcbf65b7c1b4f432a894e1b4750c47fa346cf495353e514a59cb30712bdc6d9ecfc9a211dad089cb327223a0275924836eb64a18428e1a475a3b38654245aa70d75cebc685ab77336133ae201eee76d0ff473b4a1ce1f2f5a0437ce9f1fa49ab5cdbc6a4447c8d34671feb033242faf1af943469204530504f26a8a387655467c11053d694e8872553bf2a29d5ef66b4f3bfaaabd1551caf9268d9c934ce3c5294a39c9347a4e51ca59b34e5eb6d3cb3e4d94527b722604e56d94933452fb51484b9c72d6bc68489668f6e2e9359e33a4724d10668208130496204608224a185636a3992023abca2a229465e905107980f0e2f4526273c50e31a3313b6fc6220fd81f5d3599edc93971ce1feea0116307959b578ea11ffb113284f65e5535a4aa214fb3e107346a3ff021674842d6b8d9d3ab0aaaa968a8eadcf101102c9c049cf60d104e0001476605c8fe51929beed36709a33e467bc41a3993c9aa104203c638442673a3c4001e9cd1014d137baaf4c0831557b219ac7891c4141ad0d042852b5998356511341e82c00491a4ec6b502be040e3852cfaa1a12124896684b26cf385872ece8c914412787a9c321fd038653eac5104e5830f60f850e7f48252c307373dc839812cd3edad958962902dbf56966559ce8cf80cbdf1810a0d5e80818c981dc43c61c4d5b8831d2c2c88b5a0821a62572318f2b46f7c20d5d4a0665f8800a2614f9c33b2c0628712ce5ce178010d11544409a1073ab31a4e234601b0d1b8b0e12993b039910349116c86dc6e43e418badd86d0b8b4f0a5d6c20d03202982cd105a951648a8b5b0c2006c900cb99df6cd1939361dceb8a99d294276260d6d68e895ce8c99d5ca929c7d71c6cbd40d6760b8dd867ee8b46fce7c406588cc4692a38c48005b66ce1840bcc931c3050f226cb7dbd09b99737a29c9e4f0187a336f6c3f453f34b46506cd00363364826a6698f962a6051c66a8e0e0814f8d873c321ecc98f100068d072ecaada157fad3bee101091ecce000642b1a89ba7e1ee87fa08cff803d958a46fb1e5cd9e7c1c3f63c78fca854749235a421d97ba541e7accd6d688674cb612db974bb915d807a406384977d73c6ce39bee9a1847344eae3b881ec38da4f72f30aba018526afa6539cc5209eb66493c310466f465a9124b43274caec2953c6e9d56447873765e81cb0a30623346eb5f20c12ba71060905599cf8e211790ad910d2123bc53140496e7680b21c97c0c00b1f3a3664497346149e1671c6d0cc80c59437b32c2c786121cc0d2d55ce324e2051ca82cef2b4a5cd617394a5cd513e16af1bd02d28e84635ce4af16b34afd95bafa05376743bc51247046cbabc2923c4390a1d7940b44ae28bd686ac9645193acbb33f90904be2439d6249e5260c3b3fdd102168faa3d2d0a824daf214bf6ae8b46fca7c39c9d3be2943c3998696a228caa4508444992a462c8c19b9a0469a31f6dc0900156438517442124553b0c1c207a32d3710e9a9a1088c2d4532ac4034059911842013a5080d3d68f01875e1c2a8842a88ccccf9e08d510d441485f003119d1c8886005394420c3fb44034c698128c801833a5688401106d6186510e5e1899b982680d0088c258a28809238a70088228891d8cc8d860144317a2385756107342910b623ec0c3c7c84a008a9400a3042c88caa0b3449c22219028aa41cd0a66a0186344c20c45642152c30614d2d81082910f7d944883280a3188c8d0a2e8ce14559a30d2c14d9100d6d4e9c1a887324403f05274e585a2322a1011114604a32861c4ace42902820c2c5b189950478427b850a2c8ce1028a06183439f2f4467c050e48585222cc0906034068c14a30f0400a70c2bba2032c38aa22e5014f500c7c84b117d8038420724c21075c185888f0b4568be98b0c3172a445aec299a724691185f1871b143e445146098f872c40f698cd4f0e08398a22364287a8385688d1aac108dd50042919435e2a041b4278c22006461148214443cc829126b53344608a335678c702043020d455b6831c14b0a462878a9a2c318d1126a146d19438d3b4641505114c60922344910e56163d4820f30dc50b4a606305b88d4ac6024020d2314c1a1210a91147a8c6ec05314061745495441e4c41ca22bde143d418411961f8cd2dc6c399cb9d9c014b9e15234c415231966b04224c40c2110f53056e4451e2df010b9e9a2c8cc1567444114441c2d6dba5853c4830f45e040540518325c8c6cb812830c569e9021842f634400c85304031e15ba309ae18a2951648963a4a54d510e6b8aa2f84014001c8ca4802902814b165776e862a5688c2e2118a5306674268f1e3c467401c51544604451d4441c1ada189db0c6488c0f443fe04074068c145cc4ae0c808b15a32a5c42286a618cc88a3c4472f014e5e9a2a88c2b4088e24e1ca32b6d88b858b3860f4533e0505405182220b810a571a5682c062b46616208c1ce181179e6e061d305d10e57147189e28b384654da1419b1a6a88e0f4459e060c4031827b83471850e0c5688f0c010421195b1223579d0c043e44317446d6eb52bc4dc6a51188d20a7484e127c88201223883666822833e70b0f1bb89c5e3cec50b1050b0f1e6fb6a05053e25186931ca0f18de8dcc6b740a2288aa2588a221db1468a24293e29fe179963144719597a912f96394431878894435692650e9a971109aaa0d4288a34470e29c6f295a696c81fc9f18d16300ca85171936d41c4c4adc4a288869bedb46fb4d039bda68aaeb83131f5c3b2112d1ba2d3bed1c283161b4eaf221be4641980509631840a38ed9b2c5188b13394450ecd5684508d871b2d4b985996196632328b0b4165961182c8ad212c7d82b09c81258b1b9d17b6ba261895b0286143686b680d501096333f4034242c646c3f46596e42a77d8385cb69f4c2ede7b46fb0b4f0060b0a58c46eb3a207f3f3b41fa42d72960690ec0e8eb2e869485fc4c26d76da3757ac181a9a41614b22c808d9d6d02b09d91132234816341494430d0f2282685f1b6f6faee870d268b420251e396e41431f1494a348002ea7170f3965806e03d8395d734872345ae2e665d92419828436a3d1581042e60298048a8c4a4868341a8d051f4e1a8df63c6833a322662539b24132e40a0ba797d2d905909d31c76d68053e2b4c318243a3d18ccadc64a77d73c5cae9759b52a1ca59f4c28d76da3757a29c5eb7733694e356c4c3e8cdcdcbca9960eb36f42ed4397f4efbc6853c2e7c717a29d969c1a80a23238c90e0c1c3e888190a17a4d9010f134ae8f9c2032064e0210a38329c31c7ce0c4b1a54963c64466c72c8a49021a2b0859d2cc41853c41820c688a9a3a68a28568cf18a0b576e604400801923e6cd1055e8a2cb9830c416a0182306a006254a125fdab0f940cd15256b761873e70427e088e0020b746e8071630362828b2f79ac90418d9536775aa8010d34264831c6ac0517941020cc1825317bd0e033464c028e0372c688d1018c31621b1b14086398be881963c4983352476316a009608c18314d0362c408606c0c51cf1469c630629a182306fd76fe28eec1756f4d846ff329bd38ecbb5b777e3335b0a66926759c36f58bb7efbbce7a937f7a6fbaf2d2cbfb5fcfc2f7a91955d7c7fb3e76824b31184e7397ab77779c130ca610fbe3f7670ad9bef58fbfd6fdc7efef71986f0c86f7a339bd602a16dfe68fdfdfd3d36ee54fbc1e3c2053503f9ea87ea0d8471590dd0f1f5140a87a3d9ed317ca9dba46ef57aa834fbcaffaa9679fb57c78b730eee5f8f00639bf29cf49fff932165a6b77586b77b0d6627b6bcac3b789a679ddf530a3a24c2927dd53b3134e2f8fa358f565ce7ffd6ea1186c638e9760bc8ff07806e2705cc55fbae3707cedb773619e7ad37ba57b2ad670eb7e51f52a49efc641adf402b7cee2fca6448afc78fd06794671b8be7530b8f21c06dedde3c3bb65add5591c5330c393b5a38e246b6d1b06b6d8171658e0a1be81a3ef98f77a32a70516f806ac1e5571136b4b224a3343ac2d4f282128b14a24651026217c9b7d6bfe74637a8b65ad85b276a460a94abfbe0c76b1ac8aaeae9af70d58bd8ad35cefe13487a69af6d439fa0d34c0db7ce4c15a1bc5de9426b0567dc278f7dd135775ee3c9ca69a474117169173eb24f2423e0ccd4a3b948a2cce34e14060ed8867d4624c47aab18715558875c89419220ae98d33f8e1ee319ac132faba7f0f0271ef57fcc161298aea8508f928a517f2cfc00d63c4d86039b0d652b1371e58f836579eb72f4979230c17d6a8405c2194a20899460c0650a182192d886c10424e31a7870e18b69480e69c22d99ce0dbf2844191312b4d640d6b8eee0c21c954e1c4832441ac480418640026ce074564908cd24bb10b82c6520e924917b717f6c8b02508862032c80b372f5b7c9142d49258922f07013cb72f4ea834f12167248c39014f0f43b61c80c618461060499280f152e426c62867cf0b1b48e1054c112177c61ec248d104d7132d7870a1cef842165dced4b98200146ece18054b011504408323ae2be08c3d38cfc40b4f3c09abc1883149153e01d07106d299459a31ca873a6bb8babe1082093363016a20a165d38085cb1332229f1a5e20628a0a202528e045c4c28a235cd464e992d3628bd8a60526334f0670a50b0f2c8864dcf8e03ac28d0b17345082a802081680a082052f1c4104155168a6eb8e0cc32a86841b884fceb08569608683af0fba78350390862c5af0082148c5a4294432d003822680818a422a28c94d5f88ba98c0da338cb8c0480057508cb474593a53dad44e38600ca6c25a35395881ab4a7053268c9d610cde0c51738cd893646d152e5a490be8d0826464ca02a170a2cacb982fc47ed67259e9408a090dc07812b4b68acd152634488246000fd6ee88e20090303d2cd8a8584b96090be30832bba0312fbc9853c36d8c892d43f88cf1d0840f3a26c8c200dc84c6589612d2c0b1e9ad2a70c418eb20cd163554051c078471678cb7460867ce986295b051670c4b0c0f1e593257e1893963e69d27372398180832833396238b0359202147030fc818d147004be4a000131c15d5439a3e578c502b43cf9a1b047c99e963c40f45822b5809f3421c327d6cd063432e4e95ce010b5efa58f972851a2e68b64063654b9f7e8500843c824b1da861a14f052e647d50420a402cc0a4843e0e2491664c3735041417953e0a84b0cbc9d20d202061833e3f67ce940923664c0c4d5f034f00b2d0b382154598b140ba0614348ce94d20949b420d740d20a82093f2e060570395276b7459e18b0b13412890840b5d6b7c70441545920e7862092092acf145c5cd9c2b9c40c2812045d6d8daa1cab787c9c70e270859c3079ea91f2c3a98c86c31b506511710e22c7165cd15349ad6b051f40fde282005d786071f2e5070010c5d85048b56119f3744dc58a052b2a7c98f1c7c7490224480e1053d30e124888f0b37c0c14a6148181770ace4a301185b9084099041c21b3e7c2e0c50c85829f325893368f039f284044f94a298689922069f2613e4205006050ff808b9c30787104d52c0737e0120a8ce1e35c6843af011224d1dad397bea8c09f6d1d1460b124a586b6d1f387b86e0e2803864030718b9c25a6b11e821b0655115f7fb1c012854c5b71d7dc4f85ff7a938fd9e7ebc75afefdbf96d071d6badbded10c3aaced2bdaa730f6add7688d97477fb0adc81ede350fc43f4e29d5e2542bdb7beda7a6f2342bdb7be12ba41129142889526889549664c2fd2d28872fb22269305d11698224a294354a923c8cb98d9086265125b013f454353c46c149415903b80fa2ca876a45635fb21830cfdc8de88d5d088e57812c5f68384e18ca02bc4646362b2a003002016d4918410b30dd9186941644f3216aa100064c01529650962378e6e1c914e389e6a4338c6a4c818a855d18490374821352f62a492a8a7d9cf8c8c1809343ba06c028a22c532a86cfa3922638046c1ec80d9cfcc2b2847500edb90ad001a05e41a52660790394891b91153c0ab047e4e01a31217746386a31449279248b915a46336d29cd088685932026c8833205256069124399234b246da7e8a4a92c68426c08dad1f1cb4194d2469e554f9548a350348a22836046463c46c908982635612d58a4829af9082947b3bd6a70b29e83421270937ab8f2b05148c5c39d0c00fade221e329e976a346b3a002adac3126e638818015282c578668b9228a386dd6f880039827730117d66e85ef34cc28a30e14738e38e34107183ca96002adac078e20b04347016982f000871b3ae0206754a455046c5c610514a0d8589f3c6a3cb140470138a26883c4106b7ce841072b278890c38002275a595871984062093558905031459c3647ac11e28b17123e986246195140b14688343b5011c32896111c42656c71e78a248e0802083336a460024673183cb1c009969120b8251c4271984802861752a082510c20c03a622488dde24e1c269440220820cce860830a2750b90002ac0682e07e4c452d0980a3b4635bdcb1428924903822082074b04185144e30814a16b90b9e38f064f2d99324821b36aa2842a10412267002358309240cc3cad8c6c83ee31ab23de3006a634a31a30d2318516a8c22a14413c8896480fc0210188b4422da01a401e250a9432cc03680000290b7f246908d9bd00141060015fde0b0d16a33da389bc964a46867547e14782346b3d16ca58d1a15cd461a057545c942aa29cd7e6a5565d00db10c9ae54a212944e228e94cd12236b331d26c65939051d50d623f4d82d0b2c94694718a48c6a414674288106b2049111c53c6f2a74cb294478c0b294c72a288321c0e04864419cb0348a7a078642563000ab1d90c812d2a04dcb8fa807c204a086206d89c04e508ca91c111028235fea38698906e7b40a7c28d662385645fa5082544a995366646e28d1e51ba98c1c604002a353d576c8c40b221386840ca08b00539481940a6f473e46781d99392890644ca0f323c20458c95026e50548478494630420c68edbd304548ea86d8a4880c4233802aca0f0dc8eca76683fc81f50069f473440db11f1a9610570d099191233f3624415ca8901fc474944d543f33dc0042b31f202348187c11fba921d9b123414c46de64e24c82d98f2cc88cc9ea0a68b525d590cc7e68060815b1f128b36c3a72142d7040014c35062258b240949f9a49eb21d4e307d6162da8a41d20c39154b6a032e987d06cb49ff249164159354494119147b202a06c351e09c44890a1da4f01b61f518864014dd5944a1e258f991831261b65111903b21d361e65d18d28d2488a98f924923dd2a924651a2061b082105ac5e106396c72c0418c97113ce0208c7ff5304f87c1930a2668c2846b2b09044870553d94165020812f9138620c8f185d3860a500d20106d0ba2c0107cc17098c3880332220086866441a20ca8401e3850619bac0c04429e9c97c414b17d7181f347615e021634d4ee350c14aee5905f9a1854901851f3629e8d8100607844a470ea09f1aad88c8871e9e60542b0b02ac23467053514b02e0b02154c6708208210860c7871a1c6e10e38193263fa88672944dc8094013ab06a86cb4990e2000b1c30c2eac4082065c6288c18a56965590a420710f3980514f59c693317a5176316221b3234e31463102c00645d09cb18931ce9866d481c461fc2abd441e2202352359919001a38e910031078e0164028c38c45b69630492fd8cb65a6da4d164632992a238b3d65a06acb53fd652590dec6d68caf26daa79079bac158574e4a0438ce7bfe990e0a603cb5abb80bde948e0a663c8eabea77fe995fe319ab99a9fe7eff9830cf7ddf7c7fbfb5dabcff3f7e1ae803c0574712b408b5b0150e874ef53332fa7fc55fc13fed23720e4951e55f13720e4d5ecc3bb0b4cd3b4a0f39911252b5b722ba066d58d7bbfc1a7dd7956921b01faeab4971b6614ea1bb0fadb538ce21fdcf7fb28fee265f506856c881188788a136b4530114a1090f813a684a277b8a484819ae778369794762bcfa1f88b877f3c5314ed0086c8409806c1f8aa66a089aed6da0fec4d0016acb5506ff55dbd401355b5ef6ef469e8c3fdeece655c797275f03081863a461db13a6df23049a3740616001e1d70987036bcb12e1b42ae9ea8c088002a1e6590e8e5b0c00e5054285aa073230e046af0cb8a2d240420e029043a5270d9423c11820558cc0dc431eb0b041620bff4f0c080344be4308511659dda1371d21023817ee7e90601556b905764b1992d5889435ae4170e08008d29c05c1943ad1a035de38580684b329c80a7051f90c113250110ced043582bf4c182050a83082b463959bb838a98258586589bf5d6d2e4586bdd583b06d1a28495a58185b5585c10b337ae865811842ed626ddac25bdb0d6a2d6da1f6bad387aa0e234ec02470ba07a6aceba590c38895e5c5d580f6015c93d80c5959be054226613ca1462ea1bb0fa252515a726effb06d5244125eddeea2f80b2a0b7eed2586b87f67683076bed92d2b0078138c7b3d95b77473c8c4af5b4578698ced280345bdab201d1d2ba88a80c8748b337445901a20db12655feb0240be50eda58abc94a5214ad8ed1b4a31525281d102d2d88ccde9889561445996c6a041aad288ae488441469e2288ae42c83511c45da58c012519c89343b8a422639ce664a3f2f96e358a22096a238fe88698ca2cdcac47224b1287a8da238d6988c44a338ceca510c6314c5d9284a20ea104572ac1521236936c40244511447202301355284411cc5d9288a239913c7f2c736350689563551c6836c40a48d631945fe8ca3b874834ad4a08c023a426e8db6b4630d62492a95b25124adac05310bd18ef676bb59d18aa5388a6488b591491447d93892a2d8437c126b62d0388ee38c8898348ee5284291269001b5054421010e18af88b2d100b11449d18e5be3ff88a51df3ccbe4e3481b44745887608122329cec492260a89249671ace148b18a14c7b1fc31c8a624ca44910655462100258e3fb3da4813459c388e549033d1369624988834aea2483a8936a051acd1c471c8b863dc311a9196b4e20d71368ea2507923cbd180711cc59958f6441b0f1148368a32d1c60fa2118b06641bc55d2d88368a128835512447b1149188462390b168041a69a34c24c5511c459c6834028db4522465a428fe10495149b48941a2280611778c3f244dac95e2288a38519c1530e610cb5194952290114824c71d22498e633996229071140f187f449b582b47511471a2d1f84323c5b2568aa2f8e3c521a69a10920199488a36b11c126d361c9005443bc4044a711c67230d8b3702a0809088e2288ea2283a599a0dda0a3c10cba0800cfb14c8e0851c0a8eee88e1e4667103132cecd8aea3292c970c4d60ae1bc72e91210d0c4b8eb49604327895b175a786249161470090dc222820320c3d01b936c6fcc04288c78fa31f910a0b710d2add580c54dfc79aba3d3b9c7461c6263b5f74d3433131d0a5090f02ba0f42e0750860bb0c7081d5c952b05f4eb196b44fac92b576cb5adb81bd01a1b9fda2398c6a5e56575b92a31515b0d65e60c1cf52d38f921207cfc14fd38f06ffb86f7a0dfeff1feaad7ee35fd750532fe479cfbe8c0af5de7aa7556badd8edcd46c697774fc055ed590dd627b6ca36c4b7898397c7710fa3bcdd0552f583cac7540fa928a83a70ac7ddcad26a6b6c2ad36c53eeee771bd87d3ff1f5dfff9f2feef3d9cea38fef1fa053670d44003d65a1ed6da32815badc8adc6c3dae7b9afbb7f9e5fc5298ea3eaaa77dfd3df57fd47d65a246b6d4c6c4f6845d65abc065e4c7e851a680daaacf9ebc3dd79cb40cf9941189327e17497fb0d02cf0c7e3cbc5febde94a6bdfc4d190cc9606818f6287a89586b91d8dbd08dc951fc1cf8fa0e7fa81b86e9a10d8e7d38cd60790771bfe9e571dc2f0fefa8a2561caeebc72f9234bd44acba846c7dc45afbc3de846678113ab90985c09ca002460586630d62bd617d725ba7f8fd09f15e83ba2d9df5e3e5efcaf530ac7b27ccbb6abe3fdd3b75e9fe5bf74e1aef52fc1baae76f557883bce3d09f70259e01cfc2b9388ebdc4a6b29d4e7b832286b5664f9baf557eaff8fd34c5c2e7f7755b77de3d52e4bca6aaaf6e2bec2ad1bd7a81afb34cf5f29ce2fd99a739bf3fbc6946b10ef7f0ee79eff1fd39a5bb750b0a86722481bd05e598bce757f5f334bdbc747797dc8259d6da06ec2d8843bbd1415dc45a6be27e7d7849a753e23bf7bb84e21ca67750e93bf6eddbffaa18a8aa5181ea10cbc46476ae539ea62713d39b18f75e5d1fff7a135635fabdfc1b8361a1fe7d930eef35a8fbe1ee35d034626f4031c0188048804de810fffa0f774acc25e90d69136b2d117b4b07b8a5b42f33d69a4e265f3bef8661aec7e5306feb9eee9dcc9efe5e7a7f78f7aafe9b5ebc73287a75d95a0bc4debcc0f032c22b78f3faf21af2aa651df205d9819f183f2c3f0e7e56be32ac89d11b5c7bafe68d795313bf3f5d1a5c8b1c31f5c619e0dd4ed3ece44d54e78d83cf5357adaaba77327550bac77c55afb0f3d56ba777f0573d7cbc93563d7cce71aa32797349f7e3f5bbc3e1569dd2f3cb7b357fd35d7d79073c1fa7d3bd93b9cb6138ed3d7005e6b00bd45da77b0cc6875798deef5c03d30ba68761fa2ee9e95fc7e92edf2cc45dbdfbaebe1fc7bbf7f755e10d72f9f0cd57e4c3c1646fbd2eacb5269a811b074dfc9553e1eb783a9c4ef74e38be65866dac32c1c161cf0dc3f40efef845f2e3f7975ebcbf783abde23f7e7fbaa762f586042fdebdb7965f214ea7875f210f7bc36e8669baf638c73cdeca1fc561ec89c5acb55778e209ac557130787faf6ae12a6b4d5467dd503d76e39ee6dea35d7c7b0d629e8a37eee1fd7ae30c6ed70d0d62142dfaf1fa05f6f495bdf1d6dc7854acb56693dffa75e9e5652366e75dddff084f77e3dd4c2cec78f7d8017bdbb9b126ee99eb2050f39c54ad3ebf3ffcc368e6e5956bf765a29877d320de7c071ff5a95a67a0d36935c45f523dd629aa758823f8daa8e659e135bd4d863802213cdcc45a6b646f3b6b6f1d6b7bba63ae7bc1a5e972d0e57498af8f81f731f0aa5bf7bea9e3dee53f542ffeba1df7401656d692d3c94d5782b5d6d4ed6e3a1b47ec2de7c6da239f9a9dba56d5757fd7aaa9d3e8f7556354f38438c782b5d6ec6a066204ec2db72497c45a3b81bd618086b5d6e4b8cbdcb8df74e53add890110187431753a22e6d7be40bc1fa895586b87ec0d03072ed873bba0069cbd5df07581014ceced8911d644719a9ee66f0dbcc3cec33ba33fbc40221d73a95abae74938888b9828e6176f9e9a9fdf1f0f0739aaaecfd57c817863e0551fff54dc64a23883f5d5abc3a8e6e97e69b8af9a774aaf656f4f96dc9e94370bd8586b4d3c4c71bf7be5f1ee6be187e51edead9caf8fd1dcb55ef2183b7926262626332cf7f00e7220eef9d3344760adb5b2370bc08217ff9e2775150355bd33988a7f98ee8098f373879d70ecbbc1efe9e7faf18b0487ebfaf1fb4bb5de19e92a06c3565d41dd31bfbf20df7c63e0ca55cdebeaaa89f4322ff77a2bbffd16d9462a80d315c252ca579dc1f0ee652534d58f7f5d56385c179aea5b0540ac3537e6eb05fee35fd7be51a0e7bc518006f36e1afc2fbd7afd120ed7719ade2da50bf8cd6974e516fc898289069f5fe1efb0370a7ed69a28ee170daeaf62bceba2a0016b77a88ab98458b54ad34bc4eaf9fd2e2142c2f4559a5e9e5597909e5670fdd23a89fe096f130c719ba00b139389558d3aa9ab56d5cb6362327f987615ab17b8f1d66d8208acb511d8db04d61ceafe1bf3f535ba7209759ade34bd3c8eb5b5b687bd3921e3848c9317acb55cf6e60489357339fcebb22697127b6b228635f5ebbad64b74ffeb59d8248a5b13316b0ef1974657fe42acde5fd209b1aa537a146b74e5e5aee25f5fcfaee27d83453ac7be8c666dbc03aeaa160ef7e31749d77ae736462f46622584879bf00ba0a42e90eaf141aa560bc230aa32816247b2ac79a5c12f6b2d8ea52308b0966a5dc56068d64d364ed34b44cdbebcb5d2f41259b2c5f5e607ae1b6e5c5cb84ce0fae0c6d50157065c9d77bebb7bbd38038cf7117ef11234d5486e5c45f675b9be06754cacb54bec6dc91016ab7a6bdea3b8f370300cabeb371d59c281b576c9de9674596bd19cde7e7baf6af58558fda67f0cd45c1a5d395730a747301811ceb10ad4a87a970481b7730ccc5d77af5c0917df7807c44a863da7b933d1e8cae5cb3cf77455827d1798f1deda5c43de550cec1908d444789a3b133503d19386a7a79e774f7c7753b00ebcfc09a39a47830c5db8586b6380614b69ad16eb53f397ba31987e00f7bb331870cdbb0ba779977dea7a44d5595c1bf7549d95371155676dac0ab586ea99c3a8567170d8350cd37bcb5a2bfe60ad15b5522d0bc4acfd218182139ed42a48586b459ad2d30742ac41e0026bad78447f612982db3ac55a2b7e3027063170d0008a596b459d0531d07087012eaeacb52405c12563a0c83116006b6d1966061bb60608c2cd0f6bade8800a1668a120020c69acb5641a6360b0039923b28c61ad1549080381a52d1332c65a2bced9491df1c4042855586bc724461cd882842412f2586b4902acc821e30a96344e586b4500e079208a1f8447ecb1d6ceb4b4a0479c83eb33c45a3bde4923850df85cd1e287b556f6828e96e5f7c6eb0aaee2be5bb91346b5aa4489a9d3bdeeaf5e5ebfb97ff4021dc7e17a29e6d259f0983379b309c53d49e5f975badb31106f4dc4e4fae1c660f8fbfde9f0d644b289c1327e6cae3c1c66c4ccc1334d2f11b3af41353f066a1dc64774972ed8578c7b697a79a62e4d2f111dee35fd5f354bf0c1b3ab1838bc5b18f772bae77218f7549cf6b07549e79a97cb15e1b85f1f4e31cac5370e0e7f39deb1ba5b7916ee39cd7155ef15abb98e7d685e3b5673f0fcba9d8bf3dcd6bd9ceea93987bf6ee7b98e7d3c0c961be22f34d5bc1c3c314fd559e670e3150afe75e54c1d8a75ea54adf26e0e9e0f98c3b4d7d3a87a7f6a46b7d6bc54f374d73bf87ce52ae6aac660f7f798ebdecd69307879b95fa0eeeb7df58bf73d7d71bc63fe3dfd294635f07d6a4eafeacb9caf26c7dc2977edc36ade179877f0797e9ed1cc51bcc33ef4fa7ed8f16ffb38d6d6f9397388d3dd4df305db184df5fbd4dcb47f1dc57cdd6b5f8fbca9eac76039cddf31eff7bb9abfe9c7f3a919ef54fdc314ef472feefce2ad555f7ebe71c7c0bb5fa7fbbafdb1b64edfaa7ffd550c861f67a44b2beb4d8ef7ed69bdb7debcc39ebb8ad1afdb79eecff30ff1d7d53dbdbcbc75e7f9970657cd7b9e1fa72baa81d877f9fa380d026f187ef57cb3f7f47d9e734334e73ae6b95c2ec7cd1e86bbaa79e9dac3aad69b4be8be3f139b598967f4797e9f9a7bf94d9f893bde4b5f5b0b3b563bbf3cf4eabd7bbcd3aa527a793a45710f621eef3edee982f9c7756abe7da795fefeee5b07f17eddd5f5d5fbbc67e06ee5fa8d55a1ce0df1574feb1dcc81e5150acedd20aadeed33a39c78fe5e8a79cfbf649ae637493170f54d510c5c2975f5a22ae6718c3e5f77cfdf74a5e29f933751ccf90d3ebfbf1fde14a3ef53339aae28fef17810f3c27ebcef5acd8faaf7cfad2259e792073a7ee04dbce215cac6bd8d7b0faf7895a00bc53edfedcae17ecd8dd1adb90efe50bdb99737ee3f5f063ee71898fbe334987bba02815bf7360e3edefdf0023f4d2f2f8bdfe759d541aed375aee6ebbbcf39567b7963b0d7fdf15ef9e3af0becabdedde769be1da3afee8bd3d7fd53ad83ffa5c150fdeb18f8f8a239bd4d72611df3f6aaf9c69d7737ee6a7eddb5fa4d1c4d71bebfd3e9ee51dcb5fa985f9c6ebdc939e6e91ddc2bfa28deb8ab2bd729aa622050f3f0c67a93e76fe28f3b4e83c0adbff3f0de1aabafc147d59ba66b0fa3a83a54efab9a7bf172399ed9d31cef20de6abe5dd5bcd75955ad4278b889ee6fc7d25de57863fea8da79ebee3c77f576aee6e7f9d7f5e64ea7bbcfd2e282e0eff3fb3d6f9d2ff7544de401ddf78b3791cfc1f3eef534ef5eef07f38f6fcd39e661f5b1b6cedffad7d79beb3ae5268af9fa78c733c72af605877db87b54d5df344c89bc79f77aaee8779df2c7bf7edfa7e661cfe955d7dea378e7150ade18cd607a073f07efeb3646d3bc5ee91effba72f7d7ff8b877f44b8a9fb3f50efde0ff705fb34cdc2bffda21cffd48c7b6657f1de2befc7b39a8ffc300d06afd9b5fa5bf38c62744d79bffebdfc1ce75ee6e120ff1f2fff848f819ad757bda63c35bf7a3f074f275a660ededff86b6b61aed7d3e67ff1dbdfa9d7d38f7f9deb37462ffef1218ec0ccc1cbf5260e0efb0ff1d733f0c2bb794e8be4723e9397d3dd1a7c14bf91242711ae4771bf2aee97a76a1e5ff2267f5d55afafbabf1cc558b526c7b9e7f4aa1bf730fc7f33d8a398eb6018fe21fe42f1d69de35fd79b28c6fc550c967dc334bd9a0ff1d7ab3a738e39e6bfb18a1f781463ce81d3cd47cd5abecd1cbc39dc2f58dec15c4eca9c32a19c7c3abd4d99c17bed17f75e07f6a57bfce3eba338dd5d153fdea1eafd346f1c0cc3cff3bfd3107f596b8558203e35efce950b720cbc6ae7dd205013c1e17818d53c5567be3198548fd52ab77130ec02bf783addedf476dd736117682524ec02710ef3bbb392eab10a09bb400dec527b48d98db7380e97e48ba7d32b2b14078379778e03f22b14b5a4a07c90c7bbd7fd3befe97a7b187d9cee32573117df26ef19985351f7542c8cdeed0b3259d2f94d927b56b132c8e955f3115ee66ade3dccc8300d064f20ee7cc8ef6f6be32d8b819a0bab3aab0b688aa8206bc16c10a830be4d14778ed5dcc63de77838c89f78180c0a03becd1fbf3f75c5c0dcd5758741596b497b7bd2f20482b5f6713addf7fb386b54c92073b0c79cdf5f438949d855c2c78e05cbfdfab050079f4f1045efabbdc761cc85371fa874779f827d8f177bdcd83d28ecd159f3558d6ae0efb959cb559d335003d73b002f06908335ef00b8ecc641eda46a1547497134af7debe0d7d642fc757b9554d41acc543ea490e48458c51a5db9f448a0c7479e3d79b0c803431eb13c48f2e0f21ce59941a1861735826a50a981238d3ad65ad3a766201077fefc9d6c8f4b4a6f56b5ee59baebef4ea73ef4feffe775ff57e7e1ae77307fa939fd8ca27bd0e0028d1ed080018d8d4613ee3a4bf74c3ea490e8945ea733755ff5f08c17ce20e18c7b4604678c668cf1831934984181193fcc20cb50538698324a28439761a48c253c03c0b3c51a3c65f0848047c533c4c43cbd980bf774e5e55e2f17696ac2e9ed5f5b3b39939ec9e479e75f1737bfb616debc663d9399f44c1ac9ebcec34ddeaaea953ee96b6b275a5bafe62dcc5375d233999c8c720c3763a051626f637c8df1d65a53c542b113ce2f061a560c2f41318458cc7570afc1f7f58e55308c62b5733cd46910a3dff90a819350073b56c7bcf5f61ea877c73ede3d02861875c0c002c613309280b1c017517c21c217575ef4f1028f174c78b1c58bd49a5dab37ffd7ed2a82bf6ed733f9f0416536f0173cd34741995016fce39d0251df74e4cd0bba9ee97df4f0e183ca82e7375d79dfb4f44a5d3f4ca980fcc845454109b12adcba5704ebdfc986c3993df3f39d9c9cb8d871f1c016635b5cb145962d42d842c9165477ceb843e74e17b3f3db7bad7be7f788d65d4e5eebad359a6a5e9067d2d9ee8459abc5065a04d1c2968500b2f8210b31597c6591c4ec287a3e93c9559d79c65fb7273dd392b9d34aef53f3107f61a102164db020b2d6dab0372c84ec786107007670b02366ad4905062615c56507898abff4cd607fdf0a9c153caa08a38a385574a922852a28a8c2481539aa18ebb8a973439d0eea40a963d629800a2ba8a0818a12a87042c5151537a6e833451153ac30853a8591b576676f520421c52a454f8a213a4bd0c9804e531476a2c062ade95373d805865d254021ee71759d9599a079dddda17a9568dd95f44cffff26cad1cc24e9994cadfe7a16e20c32577a572846bacee2e12649cf6476cce5e4994cdcef170fff14d5fa4a9f34c45fb9094e25389c540f350f6db6c77aaaa83589273d93e99573422f1f8a955ec73be6685e77cec2d7bffb4dff190ca34d4c40fe5bffc6690a5cf9150ed5fb611798a38a5a5ff73445625245ad1f76815066d4f38d7d78f75451b8eb2c73631c7c9ed17d857dd57f7b6a7ea1fe3dce1def20aa5ee0ca2f886a82b2604815b5f261ba2ffefef3ad97778159cdc0e719fb2ef0a28ff7ca1ba65a7320cfeafdf13050c53c3573bd93931300320092004006851850c800050f8a2e280610c01c3a738498d3c29cdc9ca127ea585378b710d55d6f27ba1be940bd7b42a11ee208d05413395555a8378ae25cd233994a9f94f44ca6ba3eaa0ef1d78f777bbf8fd10b7c2788c8c109a1133a2778382140136a58d1840e4d646902d8849326aa9a5040ce1e396ce48859abd34120eecffbaa751088bb96e89ec954af0e55577d8488ce9aa21ae2af9cee994c14a7178371345b514529bd8ea379d55145ad3aa5d7e99ec9f4a9f91fb7a4d329310135ef797a311867f24e4e4e4e7292d821feeafaf7bce7dfba1787893860e2745973d8f3d65e17c87530f3ee12eb12132c21009c35e0d86102ce0a70a2c081008e149c2538f64d00de8401c59b326fae80f0a6bfc17a53a5441f25f028d183123628f181125c09264a1451428124d248828c29d6da2f7b4b62c85433f01ff79b854670bf4bb8929ec90ce6df18effbc18d81ab1acc493838ec0a62253d53d233994eaa563be6e73bd970495d6771ac8b64619eef924fc0d43d35836dccb5dec916844bd561e733e13e012993dfddc3f05e7b5b51bfa46782c26d11e9faad3f0ab7558409d56ffd53df5415cc55abd227053355df3ae8c35dcd5552516b303faae2609ea25a953ee9a9a256aaa855e993929ec9dcd8865bb2527a5c52d233e1b86f9f9f8089e28d75d673a1debf8eaa7aaf58e52beebf8e817a079f5ed561f739d87633851b36d65aac0abfb6163e4fda98af6ebad8f4f28d39132b37df66ac4d982ec41bff766f53c5a698d76608cfeaab97d7e6dba0188930f6a6a910abea8a44181243acc5408d44cdde60fe078eb063ed1168ac89e6de114160eef988286b55eda46abe667df1dbc539de287a7ef1dbf5c56f3722076b4d238c4062efab46bcb5c0a1aad5b308318ae8a0882422e458fb3d1381c59a4404ad1d22cf105e86603284c8a60a3622b09962c3c01a00ac1133753a9deeb7ee6d2cd4294fca1ba35d6f6255a7bc269f9a87bba7f927fc61fa5ffcf626ccbb41fe1755b1efa23cecb55bf9a769ee3bfec56f97c951dcf3a7b9739d4e8777eb7ef479cf4d9a0f77ef986bcd126bcdfb1ce7ddf1ce55ad3e35a7e9256265658581370f7bc6e1863d77be730f5557bd97b8bea98912122190582186ec60b054739e513561acb5dc889aa0b5d662b0fb5333a4622006a679c2da9dad70b82e2161fa0acd699058b3e72ddc184cab690eb0e267d8552244f754ac61d73d35779df70e0bd37c817878d320cf687a7d38bdfd16d9b8f72cec688af3916f5abafd16f9a6db6f11a58ca2d76a639f9a7bb9a02f18c4044140a93888dac529ef5520d858bd71064a5d40a83a77dd1f882a6bf6fc839dd76ad70f62baae2c265a447211246172953352e45cf2c016931c12ae13496e8253c90f50d6decefbaa4f347ab48aa68d357dfba6afb5159a256804f0e1091fb6983ded646e1cbcf8c7fb1f6f05fec5a9ee7b58aa0f4e3dd0e9a18a35f9cef8d753bdbbfa75d10c147ed3f00675baa487a333650c53207c7883bfb113dec17435728684334fac357539dd679d48ba9ecb8812acae37b2444bc9eb7efcfe749fa5f5458c24497212792357dfa595f55d5c4c943c171692ae7d714a840b332998e9d6e4b9c9a766748582f752b792eab1aa38b55aea52c25f3c9f9a7d6ae6010b0f13a81a0335e741c99ae70e7aac5d7d3bb0b142dcdbe167adb5bd97d13f77180aa27528c39a5bbf54970e5d74d0e900654dbe84b7062afd50bd58a339d8b176f73d49cd1fe4730893e61c7ed6cce1f6f8d7b570b063fe700893a697080e4fae306bf53b1e7ed39f565661177803901b80cabc29b3411925abea4ca402252d339879b9ef1bdc38f5a999ffddabd3fd8c6220106b0c14634c18331c7325660c3157c44820e6c7062f6c48237432559c3a85555081aa95a697c833f0153c03af6bca5b67dea4f5a62e4af7c3347f4f579c5eb0bc42b94d39ac53b1c2e8091345981fc2ecc24481a963029d54fd4d3e3513f9f2e50bd79707bed86a9853430d3574d52073c24b125e36f082e525070d6c68f0a2416b86b119c4cc00c30c593390321c2143f099fe1978ccef554fe35e4e9f779c41e6df04c49de3b8dec9ec2a06c33d6afd26f4e2fd518fd37ac25ae27a069ecba753252d9dd2f78d7fbc7d31ff9e5ee0d3ab837a07dfc7157695e882c1cb335fc7a56b6a52b5dec9dc4eba7f90c93b99ba252e252ddd3ba5695e42d5abf43a0cd45c3df7b0743d8777c30bd4bd93a9e44dfd13fe92d273354d314fbdbce779ebe75cc7d734f84d3ceb1e67ea38ce5bf79c2eea9d5ef73d7d7b99973bcf5667d855f24d4278b8497ac1f40e06af9590ae661dc4c0dca5e29f706ba0e6b91cc6bba77b1739b88b58cf4a1fe42befa2d585739d7e5f9b8cf47567c744b1f00ab9b0e122662e7169da6114a3316811030ac161475135afbe7d5fd53ccc1ff39d63f8d918aaacb983a10c73498e798301050b431086a19e51bc79dfa4d3fd70f7c2ae2d76acd561a5b7ebae2d613ac77b8bce5aabbb12d716286beaae66e00b7aac351fb0b717c25873e905317e85e70b43c0fed4a26745b5b809bb4a700618ef9e16316b4d9d9556576fdeb7099aeadf4d4b1558eebc8bd1cc1fc5b997510dd472bbca22c7daf40aef46873d67e962adb5c1a72c3f6b4d1d0fbb4a741cc5c0ab66ddb25439e11dbc99977b58c557596ef6f61463a94277ad3bc74dac30debd9c10ab45ba8a81aace5d9d63144b102c376beabad65bf73cef9bae3ebcaf8871a5e9f90d3372e55e4172010d6b8d7c02a6ee6fd77a0faff07d6abffcefab77e53c7f7abb464f17627021ab0501b400450b685ad8622ea97bc5bfae6614eca68f9d283d54549ade5e06cb5788ea9f50f74ea692c739994c5a8062410d16ac60a18a99400aec3fd3c4ad7aa8001020f9eb020bb8acb549f6b602172ba0b042032a74a1c21a287b53c18a0a1ea860a610450a1ea4b084821776dd5de983187ffdebfe4d38fd7e7d6afef525a5cbfb267e7f9fa6fce254ebdd9f8f733237eee560e6e1cd4d93d7f137f126bef150bdc35d10efe0aa874df9ebefc6bba6251d5e77d761b09cde5c10e32f9dd2f3adbfe99b50fcc5d3e90fd5dbefc5c01c4e7737f71bf776ee2a0a6650a862cd2e2dad2598b77b146c56aab092c54a122b3aac35b76edab897dedc55dc790ec3429de2ddf39cb43158c63c1dc6fb888eeb7efd623497dbb8a7331fe3dd7b34af50720abc1be71ed6174f274133df18ef9e8a33d0bbc77d373d72770f33c27d37eddd4e70c15adb65c571b82e2161fa2ac72f0f3731edcd84344c30c234c1e6c3de4a98d393542de43a95490950245441421512848cd0851b6bea887cbaf63616623d6e7a1dd68fd7ef95ae09c5eafd26350337ee187dddf566e7c095b733efd3f47e698d90c008a5083988308108b42a74aa4ca992c4d45ddd958a62da0abe2e2a87a6fac7f1fef55c8e5f75c969b7f21faa79778e7757af50a3bfea61e79f7750f7550f55fd73d26a0febcd34bdbc55bd3e55a7f7f7050e558dae980743c82184358407c109103400410b04da07704c141311fe121125ad7732977447f8bdc2e174fc0687bbe718e5f9d7a5f4e6be3cf7b0cc25a5d7e5388a719a87bf6b15c57cd8cb9fe6dbe3dd5d9aea9dea9f10887ffd8740dd2fe6fa80850f42f8a0c9073cacb914a5a4f54ec39ec3887c020998b9aef5924be50b2a61ac3597500c96532a5be7143d79dfe039858d353bbfa9aa357fcda784d95f9fa264ade5f99442861431d69a4b5809c57cbdc195e37d5a597d93102342c2f4158e0087eb8a7226cadea2508912255647ac8a5894075784e0c1530776acd9b5de3fdc7d7a7966c77cebce71bf29c73f5f36b910abb95ce77817d45d83bd10abfcee9e6fdc3598d63f9e8a773e35fb7c37ed6955a851aef42baa77100a0a2aea51ecdbda09efc7bb1403d7547715ffcf977f8f77f8eafe4d3d0cabff3e5fde9d3fbfbf61072620e980e4808db5e6eee1fce3adfcf9fda6251d139dd2f761aaea2e0eac70200107393670c2c4bc5fd7bb5e4fdfad33d8fde11dcfc0a78dbb06fb255659fde6d02cab9e81398990ce7392b3876135bd5d48b09a53150bc5a8baea2b15a738dc11bcd7e0d639ac811d6bf234606351f46a109fc3a8148e0d89999a24994210cdc08800000000731300303020160c888462c9b4a818661e14800374de4e7a481549c5690e839031c6106408218000024064604664c47a831956699878bed6d9bddb9205512d5bce2d05be3a17bca4e069a1a4286c3338f7d8106ad72b1c6f454c8bfc0aa21b206132da7af260ab61c937c541d362dd678d2c9978b645e356e76e8bea350e1b8070b4635edc868fed8f4a9a3b435dd31c04e7e1714a8aaa8eefdef34d70a3544ea0e32fac1fb139c7679ca134cf12caf20679a1c5bc6790f0bf5187927779544309b61e7d014c4b3ec3215e115758873c813a72c1733a78ea368dbcde3ac6460b3da1759c78229bf45e798ff61f7c7936c11ce6b02f86f4e66aa4242a105d5fca28615dac186e75d80f085f87b123f436a0f90379892836bebeec578dab47ad7d34155211bac2d725337f195a556678c80558789a6903884abbbcc2a74a0a4011f8855288f3bcbf78c1390af1929eee8c5b4a111ae3ae2adf05b9cba32b96a767fb2e5e84784dc31b151e871a8817df3c015c032d4a3e2ec0f369a06fffaaef58031c27c017c6f423e4f53b7eff098b9f09f4b9d69729e34e700eaea67fee5afe76c87b85f678ab3b16f1ee110e63fe7bbc9f84edd208d358be94b9f4d23e9bcb2fa10917af3542200f46be7eb8b1fd9838c6643a3d1fdedf92c958e1032b6f9f1484db42527b5da761975d78e4f53192102ea82728e851322884137cd76f071de1b87cc45cf460ad10dd520f0855c49659444a9e4998efb84dedf1a1c7b95da3cd8fae2baeba350758c35930d909084f798ee8f9f1e6c14a69e283061b909ef2c1fb4e13de6db6b0a718bf08a5904c3171bd288f08741755b68aeb350b5a4f07d7605bf60b13e164099d348e16e6e230300bb66ffd255d7f2b99689f37e719a1b6678956e5bbb6181eec9c2c524b12c27069d01647286437d5023f92c8660f01a48561b28041060ed78ad50fa3561ef46870cc28d5133601d160bd20eed24938b84af06358ff26ea7870ff75f4068ffcbcff36ed0a9d89fc972476c8f16cdd5bdcbb74b3281c140ffccb7e8eb06bf5cdae4ff914085ebd0bd78ad4f796cac8827a3c99b9a738092fddcebec0d2372e17cd9b88be33d495fb6188086c894bb77fdbc17520e212bad484751cac9864892f268bb0764dfa0c67387449fb0a80674c5cf2caec8326b834d8264b6e8ac3395656501a75e0be0c09addbfcb66a650d86172cd964e1e16f48396c7c0fb6f0b48561ed911948d3f08e0190cf04692063c4cfcbd6a033da0ffdd0c3f33077a1026ddad22d6be5a2d7f5ecf6b84f4692a906f7f0010cccebe77c6902d54faa88d3d1e86a4fa09406d5813da22bb01355d558dd360d7e0241963cea105f37cf4691f228d2224b7151ea4168b281905d7598a87b4b6c852c3ec0e442ca84c20163cf9503e65795c3956bfa4031371285334a9604ebbef97d42a537ecb6fed777f5fe17594add41d6a81fa989fb86771015f48defdb1f4c0b16f0165e403e71cf0b818e5957179c0506da275b0c711270b8271bf60599de52bffd1c896716b61909c5e85d70d471957777eea217bdc85fc26045af78b7fb1185e43f6fcfcd71b1dd01bf529362bd4cea758bb7e12fc4ee150ef4ac94f54b329c08e1ced9dc8d7dc1eb29fb0820f44b1679a8a81bfa9dc6fcf4967148ded55ca57787fd386a97b1f9bb3316c7e8c4e0ce89fa1fbcd33dd47f04fdc11970aa19623ce6c7e21f06d0e58ee793839ec53eb11a3e67b17c8c450d78c1b60d1454e020870e0963671fc652feb94d0404c98494fcd3d538dd7e48478bd2f94e101f354750838e9586742fa8e3f5a9c0a7258492fce58ab96357cb35a1378e02ef1594d6778021689c21e44c4a23dfc28ba45946b077de3b54dec1eaa5aa0ee50832e29c564b051a86758b71abd2fb81e87d61e7440dfe67c37e055cf7d541f6d7d7d53a565c4ea9bfdf1d4d7338b487f7efdae9226fd06c55a7e75dae3fb0d6f192e94cdec1b38fab53275eb7517eb5813e93899b5b79105e8b3a401f3398321a2335cff100dce6fb5ff6020153ddac955bb9140e5e58e23408729ae99458130f60c0fcb21a3a21ab27de3c6ecb6d9030552ff67f996e8534e358c00070c6e4a145c923c88c780f6be0b00308b8dbefef4eab4f637bcd206fbe60f527765abbcfab5dc2e5d2fbbefec0ec92901721ee3d8c6b0bcbefb35d1fc6ab47282d6dfbf69f24893bc4438722d2df1882d3e1a1c9ec3d459482de51cd4a864b1a3d618c62662652aad94dff36a9985281db86966701e5d42a1dc9cff22336a6566783b6557069fde379ca2f45930950fc66f956a7b0ccf88662d1b9e303f4f2516e4eee8e7d7ce6f26a5cbea573cd373f0bbc12ba6aa94cee3271f7e62bfd07d5cc368e6aaacbc33f3342e4c39696869901a03dfaad83bcadcb97638e8d6102631d803c1f8877dfa78950c5e3fdc5f516952358bdaf8b39704f4ab863f1948a6c47851c3b873ba1bb8897b964c7222a3a0c72cba88a6e81f410ea6f65d1bab9d5108c5efb6cd4fd20ec6e332fff87cb18d30ee8fdeb6c0f367c053cde1f2f7401e9bc67428093abf378885343d93bc2cb0d7c684b035b6c84484e468b82e1f3e34068633dd42901c8305d16ee3ccb780843cd3997568166de78208ed60cb8256639da423017a88b66957f834b808d877e49a319a3b110d299cfb4d7d14fd462941d9f2495416964afe8f03661b8f94e56cffca0ed2f974ddbb2360d1808987f2e7226570d8bb865edd049ac928edea02342914e662d4cc934184b84bd7f7a2d9cdd3016c38804166ea987d2ad8d7ea17fce8b8eae4e9180bef2c7cbee56c1027c052527f7c87c850fd9857389bd0128bf41f61d36c2bf1712c11d7a6ae5974bc50c30be0344e055c895e3184034142e6f7534651dbe1477bff628b8ce9d774a87dcba92cf004b8ec2d152e8131e4fd2a42ac414c4036a6e5e337660328a7f44fa1a8ce6b913b7c801eed1f94714a57bd262356c91e2aeae4dd4b991e6abfaa1f31a8f84fc17554de81df6042ed85133c57947984a44a1e44912850ea37490f72bc4a6bddd8d8595be2d233217e5647dfa0fe4002a4acfc1ab635c21b9518a93fb9cc26985712bd9cd7ee3b7e5b3f403583be043bd4d55c4317f34245e8fb2191a488a07ec1a5dda126d8c6c84c49c802546da6acdc46433417149149627f170808a1ed77251eeb1108f0ff8a2bbc7f91108f90b6a6b81373b3ac5177c7e4fff118cddc3c41d246b61f856bbcc277eca7cabf2044a0761ff5ce85ff04006f97ba33e4a33fde70abe2ff6b04a53e0f73239f3c73be6c48135ae2a3071f2858e9bfdc8ef7be20f52a06838a930bec39f5f9a44be2c786e699a459859ad345ea767539d42162cbf7bda1d085e2d64e6d1aa138f5a9b0ca16a218b499198c5523069d3051b1e86616bd64749b78637478a009e5118024c8808f5edd3a4a5059b67712625a3eb1b74084f9182010a86999b935f9365f582cc41a657e6342de7e49e13932501b8272443ac1ae875d9b46cd23350452a26615588309661b839c084c71f8026159e82fb6380ad0b7a5953518badbb17006ec7cc4771da259f9779236e0c4f91c007098ba3d36398604e15d5e31a78c523dda069a77be3fcfad2347b6c3dd7a9d41d69347ac3dc9e04b080089b700ba4b8ae632583e5dd9dc46689567be25c191145f7bec212babd13221d24a7e5c0f9b5ba17fac7410c8a211abe441accf80298c721c206e4f8b9dd916a71b061759cd8c09c61e18e732b5880a52677a130e1149d20694b606b9dc8a6d59eadd4c20e0257941d1ce6481061df7b4b2d41f3c3d874941fa04eab5175bc49c7900c2a6f6f2188fe8acfc1f727c703357ead7533950ecb4bcbdbfcd2ba618fc3ad3c96728d24268aa21a661ca55caa9ae803ae3680f7ab6b5deaf914743c60a2ed14751bea69039085d99ed9de83177c4b30be47e4b3f930e32e6aadd39e597c7ea3f466dc76a4cf7d653e52e53066f8a33b37fd5ec6f2ea93c5f568dc489ab6fe3b1ae8bdaece68d0ea31879d40924fd7789795516c607003a9815e948b76bbb83eca47edf306d110889642a2587c6dc6d3b1763d942d5402886f2e877855170d4896733252721d40643709a279af1f7ba86550e3e6e57813e1a8aac9dcd7a77838503b55cc010fd7000c8ace785b4481e9811d5c40c6f83621170b13cf936504fdeafc62cedf4dc68683b8a79d9c42c27c77da7a2e4ba5973c21d91d097d1ca8135ac0dc225425e64a0ae490dc23ead32345c084a21b921e8974973a2a4b307e72a01e257d9c0a1fa4d7c844f7d32b918d9c01147df225d49c7975c6ca2c96dbad654fb4a9c7130272f21fde526750a9bc1cb99b8ca0e02279d8a98b00e4bfbfaf0750d517ae04aeec03c8dd5d3e14193a7a285cb56c83c64c301b39ebdb1de52fa23fc996ac82f3c3cc17ea95b70502d6a84a3959521c5ff70f7d2913c3ddad42f3cad3c2f5095c6e53b4a0cda073621dd9f8d12eea55c0745f808695a7a7924134f2eff224d80c43c868b16eebc6d2db8060ebfa8357b4b6adf3254ae7639ce1c15d148e7a21c49c1ae53d83f3b220bfd70e70a0bf8711b176cd066c896aa6093744ea21877ef1da674ebc1a5c59b13d371a9816ad98b89843e833d164df2afd649e8d125c57c7cfc6c3e14dd35456c6b16ff658415e98eeeccb10d6c71cbaa91843b1f1247935ce9b956702b4a43a3dd1d43dfbd35e01256795e63dcce637ae7fb830ed66ccacfb514da71cf1255666794e60c82d2b494c9fde0f7e7cc0b412e9262494aea63c0bb6bf5b789a31fbda0382234c9985caf776ce970567f436f0061ca71ae0977b41c2d51d6b4b9e5e67162485eceaffbbc5d7b07827fc7900257d29b6b05dbfc15010bf9ab2ce6fd3cba3424be5bb711b00d3127ba25ae44060cd077abcc3fe05c4cbaaef276547a3ae197ce36941cd0ac357ad997a9a797a123bc29afa0f977c868fb255dfd09ee342dc58ebc8bdd4ae9d5476bead0cd7858db3bb15b73badb7a00abfd4bd81e7ca2dcbc45bc821bb379a4b4ed896f0441c71229868abb9a21909aec717544b79b6ea2d1ae8cebf068afc34fc6338f98d0465fa180896bcb3a77f9d83ad9ec743a1a8faa72886da002030205d6d34f75b446f1925db473f949aa7334b4346e4bd19aa29f039338e755defdd70e180083ae5bf37b604f41bec3fbee06cac1b537dea2babd9040452a770ccad9d45dec54b9e917abda35042013848a356b62892af8b9b17f007519bf9c3d8a12640040ad2180e951510a0d9d25ee7062b7fe91d083edd8bc968fbcd5487ef27fff157c6e19298d083de64a41f2a4df190377ebe829f4b099f23424218805746446932ef8677d1d4c86a94917f38d72defe678318b13c077301c59a19063514f3b8b4fe52555e3bab461e2c25d5fb0490db593806de32cf335085e3adcb4a8a38c036a32909e55d71f8269295fcdb634750c08e316a27afde85b182454521404a47d36a3e104bbae47d010b9aded70e1fb7d964f74bf75cbc6f4c2e01523cc60ac2cbb6b3171e92c9b6e37ac4577fa2413136bd0bb4944aae3ed43e384e5faf097f258526e7566040e98b933280d520712451d5fada0f43bcbfc49d7d3abf1057d69e522d86f6349a173d796bac87c9e7f962ffde5e04a791fa63daeaf153f2efb0d675ccb6064bfd43654af1ee18ab9546dfab830a248feb7b320b31bdf4e8688b6a11d0daa7a501240b13ecfb90b476ce98eb0c0d7b8bd4b334f7737c0b837f94d83c0c4962ffbbe3c89661f878fb2f7e3f991bcfb97a4deb308358041c0517c7f78c9b574d600c5b85610b2534cc5bc6c60f36291482d396cb202989750d114bb170381507568ec02139aa2a799e98437ab8bc5887ba1dc998713f564f7652a1063888468a1afeb21dc4f5b5d04a46e7a1d436520fa45e883543e03124e20c880fc82d5a925ecd30fe1238410206e699e95622eb6e1014e59e6cfe9616e75ea7a5d88d264abebc51d84e05cf7fb197c831343543f04748b75bf3afffc5a06f68108184ffcf116c0b2ba3677f65146b33f3dc87820c2b3eb1bcac686869531fd3fc90725d366b0e76bab30c0046ff83d1621d50105605b40d97e757ce65e55a33f10b4a694e044780b90bb07309e79db7c4d80135b846e743e1601a4917f74befd617c26a2656cb5d510aa3e9395440c67b0d665afa3607b00068dddc027c5297cbf1a67cf911dd57d8cd5acc73525eb3b98e21e9439d509b18c0e6cec60868bdd0e2d722e20e9d0a1beb8c4dcb7f240f02602cd7938669f04c03c7bcb5eb20f46465fe422cf86d41a469dd8f18d241ba2642fadb43033455536748e0cab33861b72c1dfc590d829bb23ce08e2ee10139246b350831f39679eedcf41802cffd6e3316a785a0d2749ebf4bdf986013938d078f0dfb3a321df1dba3657ca0678b625e5a7746be02269df2ae8df8ff319a7aedbf4b493dd0b5649ab1ede2013de8a32d40ed37acc5a620ec958ae3666fa5c167afa5e75d86fe2a4b1dc8df2518a935bc431640291fc688d41f36c22cdf40c0a1ea64a81d91865be1e8811388517acf64984a78fe53417035893086d106519c00e0c1a581689562167ee388e0e82e477a0ddde653d4fd5a1debee39621f35b2bec4c0f5efa37f082b66ce7cfdb8642aa97185d291e6d5d64fa68cd08484e43e164e722180d336425fbc2d53da89c65363893273edf72c60ecb725e9f60271336ebec4b514f95dc3cc97dad1a329ade655aae2df0c56c42fd4ecc6ad894132adc75d0607f16cf7b4da2eac46145d71ce05b8f078d9f032550bc6460c29aba3a17d921be07640707adb36616047c99e41b41004e784aed7e75017d1a0d6234867634a7e2956abc2b6c0d8bd0649e2212599ae9430c59f2c31eacf19a4829e9d48032b063743abb0cfd8c327ed2f6774f6afd1a352034a8bc50e33ce5f0b934f628e97fd2accc1311ee29d6a644d7979d5f35e01b6e6b1388c98fcdb8da1731ebcef2200749de4a8f826954a13f915254dd15be450458e62ba9599db7df2ad3539715fcce2deb38b42a63b4ac69b04344783e2fec2f8903f6ed257abc4677799803707d9f37d313422c00c5fc663247f51deda4e00abebde28b711d7a067d23fb13893f8b2c114f620803aa9ab6ce0d80f36d87cfd6807fbc4565837100f9888cb50d6abc8db47566afa3e0354f12816ac6b3cf1c981ad3d0df38af6b7e0e03fa3c47f54931dd0becb7afb91c6f2269418350e18e96108c16178ca69732ae441690a007f47f4bfafac7586356bee1ddedb814c38283bbbda65b3e399368bb1286823dfd27ac570e4ad297d7a36fcc1957885dda5854c4fc446a76a29845ef37a35b42799bbde9cc6f5368022bd36e00dc4b9f81df5d2e0c480f67ec97598f07ee99556f8190962d33fd8c4d71ed807f3afa19888fdebca1e50430a7fea19d4e98b60c33e30dac5c530062bc019c616dc5fd95e9c1baae988fa2bd1363e0c3aa84d2684859a4c91d8b1e32e9b4666a08772e1fdcbccd2d20395b05ba1da04b41958fdedfc90fd0dc70d4fcb87ae2e96f0d7af5333bd12593a63f36c91bfbbc41ec4c865dbb7e7bcfa58a46c996cadf41975f41c7ec6a078241c09c2350f5e8dc4fe86c6670645991ab989a3d5f55c5c6750318bc250db814f09f3260dd7d29c865b8be44aaba68a9e779c4acd6429344c79d3f4cc5369fbdaf1c5cedd40fac8c93179714af275e51f7e7409db6b04820844c822f496d248fd78c28df644ad717dd5183385f14df294da9b0947a68388908c5ab12c6c982867760c63dfea768b320879f87d50256badb022e21d87e6542c31165fa577ea8bc60afd85ca5baabcf2a59ceeff7f472dee937222658072ac97511370752c69db542aa38b968f463fd5abefb35f654e4539d783d14d2b014441007e122fd09d69035e60d03a350462c35818880ae96acd6f9f5b6aad6be8c6274412e9a97006b0834cd0f0247510c147598d98c25005d28ab0b5a03e08f7481d19c18c77284b1259f969c3e75b6b9b33cfaf2c9e45c300eb50775104d5f20f9cb42f0066e08ef7c081296a0a39366b2c38c6cb9b8df347f9af54a0d2f2e920ba0299224c8f5856cc8f04d670b395a21602a58e490f1829c2fbf571d14430f8def2d4611bcc88d435c49540ca0fd99b884cefd7a3f7405c7b7728a418ea85e8b00685d4ea26c9f870c0b1a14a194e8c0c32e2b05edbea113083649d9d50e7409cd0b8ca5c3bf6f4e264c14ccf4daf95ec204d7e8175029b92cdbebba39351ba229e11758e05737ecb384c79198fc917158b0551c22cad0a544e3429b6889a242464faff8ecfd9eecf912fb05e326abf6b5ff78497cfafbf5423762e59bb238db32246b46d12bdbd470f82018218e0917759a044478d6b4996b24012c23da824403c102690a8f8921fa9b218f7cb06ee07862532d29cbceab6475b17a9a3fd2598b14d62f3b6b17ea871372ec77d02affc358e8b674164263fb303934a15431fa5c11fa5ce1fd6ce92a0ac0312e7bf5265bd7003c06579abb17e9996860e83a066fb0eb00bf169748ea06ac83af14d403b97280a598da81cb2c9dbdb00ad8a7459126a5ba10cf1b0149101b2a00ecead22c50b55577f239cc3f70c6566e96de647328077c17fdf842de1375025620027f095a2b34bf8f67d50ecde41ab2a03640e6def747af185ea7dd57017dd8a28b488689f1f2415d28424c8a85ac097fa19cf0a034ec9ea4b635b0e1abf94c31cf5e774a5cd4194099b95d3de9930e3a52c02d09986eaf41214596c80485bdee0e6fd72985df2e0f612c50729dc78fc3ef3cc3f4b226473f47d9780341c1502d4efcd40c7b8b683ac5629c9998825fea1d3f60a171a04bc3f44d08749c1fbee7256b9c067f94737ef716d5f3cfc92f5380d90330226b5b91334b75d0f2f8630421ebe4506d9bf496cbaa96c6baacda6a97ca939fe8f9f51490146e18d2b1c9a00401084296b15f3e38c68574eca09594d872f617b62b568583e0e00fc98ce73f7366f2171b00d5c469ac088cb0c9e0f2f40cd5c535b38de66f0be91d91da27d0d102b3a52869fd13e2825826c4af216cfff7ac20f9465a33753fd28e2c6a389bf646276b555e68bf517da9013f2953db1e8489e60dec9a498fd7ebd82d8d8762437f57f25bbce46c469fb0fc66349c935c79010ef0b7a992395c14ff5f80e5856190fb1764fb5f85c7906b9f4c7ed134fb4c91fc8e872e728bedd43e6dcf472fad9de807663f88cf8837b3b58b378ca5ff2033174f266ee257cd032d2139d0434b520a6f789a5d2456386454df5b8e8a35f80ff0947ba1a875231ca1cc3653ec6b8b242b9479587c92e20f958402c2bfd8fd6849fab0d600f43768e5cea9d3ea27d940608c348da7b9f4bf2782697d3729c19a135b361c50e141e0f0d72dab841197feb234440f994c481810e04b69274cedf300fc3677f99bc33b35d1a0c1df8e81d08dc4048ea03f7ad22c1992f55a24cf34489dfbd0b74b8221fe35986af9ede310bcf3a811096347caf5f89b6dc56d9bda31ad642a5a7122d1f9724e402ac95b651965440616d0e413372849da84d3d47caf820ac675af9f4cb2d6d0e5e0fe890181e81ea7dd562efbbaf5e25c59e7ebcfdbae88d90b5fe8c580228b6f2fd044dce1e6a412ff1fac4b0431b3b5ce6a54ca43a463794c1db0987869532d28f5ef282e80fcb61eb23d748daf1515936ef3b1f10db07c37147447d103c4b632fb91d8772abf8dc947bdbc514c35cf9e87fc386e381ac35ed9dafc2f92fa081d0bd23b4fe75db53eb86fe8b5c8d63bb2ec2de8244eb3028631952169538434d85ecd094f647d316ff4f48e604ceebf4b4596da7eec45b64195e7a1d68819aaf8e6c5038c4226bf69791bb5f11be4cac61a5694b50379c44b2f6d6a3a695f4575ad86baac900a691312ba7278d385a671d311deeea8e23ff43eb7b2215be857cbe0199dfd44f15ba838476ec6a2fba0273a7ff625c5f6bed71ebb40b021dc5326053a2e684ecf8f8f46f43d05759670a0afdc36f20271dd2f44c60d88dafc78c29d1a79e7e34830a25781076ebda9fb98963bcd841048566a38acfe06257683b3c730ba50a3b849bc89db29c209a35983c54c283e31649197671004383ed5042dbd9010f1f95ca7dcf2112587f0eaf98a70961bea98174cf4aaa4769aa8315802902da6c75ef37d9fa13301b741ec8152d4813232d1f2b46383a27885341d51ded2737aea0d89fddfd54c09fc27f8debec1005a52d73894a40bc0aa44ce675ecc3e97073a636a52ffff5bf1aefa48a289f52ce07a58016ac618cb3ee00ff8105eadce076c1ac574c4cbd3d5f70f106d7fc2cdecedf5733504a3a33796944af0872d0bbdf1088676183b4def3ad017b03755fde38bce2a354a598401bb17c96d6199a462236e453820bca6bf2232185d0768d43ab97603866dd16c44e8a948ad72cd90b8c0ab7ac676f3354320dcdf51fb5f056333da74fcee7555ee6ae4fe041c47988f662700395cee32c8fe2a339f89564d16001096f3e44f8903f0df5d2c6718363dbb6da831d1d26355b191b14682cfea16a10a7cb873967ff9897fed47ce5f25c7af368d7086faab8a92d59065a974b5c25f1a30d4844cc88dbca38bb6db2a74d019121c9dd6f191a227f7d453308634b5e115d817f23e9caa39193e7f1a18b5e54634f392d5149c3086fb8fbcbbc2605c27cdda0b8e24aa02eb69becef783a35a8d3a269d32ad391ad38bbd6c1c6bbf01d6eefdbca0e03d08760df3e84bddb49b012bb80d6f5bed2e733e5af544a10b5fa335c07b703ee9e24469d07076c98f94a627787feb38a1eab595a04568bf759dfcef11de7fab8b7fd5d60acfd0e5fc2e54ea7ea6811d94356f375269a7ddca0ce4381986aa4256935e3154ee8f56b444396aaed63cdaefdb991c963c535b5b8d76a0416bfc556adcdae00ded3649b90219949949f9f3fef5a237a46551e4eb47271304845a2a2c707fbea10cc7d007d1acdfad31a8dc4cc535bb577217355855dc144e47bdc74f10de2951878283320f06af1ad7ac1d644e0af30ca1108b6745b4bec0e512943450a458846a7f65bb1ea6b59debe7344d57dc69b8e7621e63384ced819d9130c62f022ef16be333b98ed07c60957be308e0b808cade01fc761f0441d7a1053f45d64a256bb6cd6cb367fd810e2ba10bd7b316b31f8c5d67e387b99b59a101879058ce4e1e2a09f0de13df785740a5d6a27d835e33b0837d6007e9055cae1b90d74d859d98f94b7b3526f23380d2322fb37793b28491dc21634817fe4e41209e37bcfedbd8fc7f522231a25c5862dc748f8a2708573f4fa08e02b58c28aa6f69d66babfa70d2118cb703e1d20ffe5ae3e7bd0981ec73d260ad8982608602af35bc7ba8ef21bb5b56044161db629877810da15b48452e9d2b14a80e9a48d063902e8b882224618587f0919e3a918dbf6e08a3550fdb45fc7a9cc9fd6d253caab4a0732af1af608805ce07b2b7d0c2883101aee9c5ab27d7d220f24174f7e5f436881e6c326bee0b7db89dfc62999d0ab641342a4de3c50a4c679e7464536ae73bb163f66ea550a3579ca64f569a7584ed3c7fceef1439700ce8f24fe18fbbfe4b43336d4c00b5836016a2b98e18687380ef959943325a3cf104e2102bb16d7d7215dd104a9e63ab96da547b41307a01c2f74f6300c29a43f31c6d4e14ff205eeea8fad6e2a8b9b9e191289b57c8f0e96c9f5cc5c970c27ac3006c8aa7a03397eff1ba8386a9f4e725b8a87906aedd0b32e1f200db9b323be891a79387cfcb8cb00ecdd300035ab413a6ce9b862a5d38d145acfbefd0b1c0e006571483b4263d88509327b8bddc36d30f1b5110049d7bc5e075302c4d98cf1195521b9cc6de311e4af2287c2f68fedaad60d09c28f11cc8a70956b6ffef3bdd6b4c40d6c61775838d74d6207edfac5283ef5dc786140f6acdd89af8a539fa85851f45920a7484bc14d25a3272af8aa842bc72e402caa75a237fdab8edbf88a2ac06118e8b37db8a5b16fa6eaca2fb43ca9d1d1effd73b8777c7d70290e0b0ae7976cc4548700f41b9b03a3d673b5b04c32f9e27dddfe5d0b56b80a0483a5a5030a9ab455c85a5d695c1fc81c4950d1d2d392316525abe86ce432a742b6277ea9491eaf8cc98caeeec1210f93738019facddacce875c0fef87879be6c28837ca908515cdb7d9ecc3284bc00ffb65bb7e80be3bc9ad5ef096a7ae17ccde0bab268d385308a726eb3eecb23763d7987b56257babd099ec12151fe4f0b8935041070434ed9f4110cd3aa8685867ac801bfeeb585447461bec42a4d3f648448d83061e3aeee2fedd6dd1fd731a4e898d4edbec16a5d5cbb1800c7f2240669fe5a8cb905b2610e315d92c964235a980a8056af3ebf22fa37c830da99a0567bb02ed22800fa917e7272323de20d58fdfd324bd62f2498ebe124f8e2a9e6f72519d9fe61816f85576b713e17556ef7c982e0e47549f2b3c363e06ee2de86d2b8d92552b8952f7d6ac4e9624f9c78623763c78ece13e0e22ced5612168dabbf72fa8a86b22b5aa038e24e3d65bc613d01cc677dce3420982b24bdf505d8cab182ec795fd7e277ab5cd7bd337f62771e8d6ccf17041967a9269ea4e9425021b82e3dc53c1e78c6a5c2b6ed9f654fb79ee0bfa8d6f014b9f42a60dd8f320655b3ec9d84ff7e754d9877c85248b80ae4c3a8e1c62990bfe2739d21245b99e1f2d5aec6bdd3abd3c69fe836e491f27d56e04bc5baf63f4dd9c4febb8893925c067f59d408339739700a231847e04e60af733711a383041a9bff828d79393b95f0b106586848ae70fb0e49404b3b64a02941b6c854ad6f00f2b1ba448306482588f38983d2038844096d61fa2274a3fedfdb73e44a9cce364a0bb45405078c0f050edbf037641fb0c8c2726efe70637518549651f681eb5ace379cc0333af746169124fe05b2c42c11453c3dbe2705ac536b2e59fa040af5a6929de1e3558b32907de19dbd103ebbcfdc989c22c63a7dbdb643941304f129f376c676e6a91075f7d3112d94994af8d2780a494aa6c43b6b5d324d5aa5f3a8ab67040e10e4b3c0da6e9293c8e347d97eaed19b2153e46ae90f7f839512198c8c56a11311202467aa8a1b25b79f5f38a50a23c5a762973e2b0742e7f45d9de8b0a00898cf2af1b6e2cd1c27183e5aaf8f8306da69119d4fbda922f106602b88f487687aff1bacda4529deedc2962b0544f859c2d18ffe11c12c0ef642496a63a91b4d2ce5b5d30b8c42b8db7229b7b08484ae598c616b1be6cb16fe2b05a1701736a6857a81bc6d661d2844bab967d172391e34e8e25550b0421205d6930cf688cc4ac1b481c5e1d745262b64ed80490e1605fe4d9903554eade4646b8ad35b2f9cf3a5d0a1f8258ac512e4357f3e8c2907cf7df8ecd9403e02bbc5f04bde4cb06b3e4c3eb21d8f87a9de10c147068594a7b2660ee6c8a0f3b3a132af77ea3940d177e6f7b08186d611c9d971a8050c9aeddfc9e33d8b9b6d99cdf1590895e0ad369a1c5c3f3c8b95a4e730253ae3c8f8812e1697330c2ee201f3e1b82273595e31ac19694bb3548c75cfd00385b8981760238218b0a5ea4894aad0418f8fc420f9dfd79c267914da0a4a69835cbcd9049c1989b62a433e4423f8a015e60176ed3a7ec9234d3592ec5cf846d012e2b8a0f2fea6b2e19e6233456e4d6334ed865c43e957db77b34978a3608c89dfe5c00e80e2372604199f2605860617081c39cfd765d4e0ad8a183243d78ecc607228eb6977c7c78d7e9fcfb6289fe6b5272578e0c24a87d6249e87938c567cf7c08d31944d88767a0fa09dd5a7e27404861bf4c52025e45351ca96c4572a1311b74f7c22388773a655eeae062d68d0052cb4d281a5c7a09418070047f35b203ad28950cb04ba7fcdf61c7118680388fc75bc146d5d86234a1f25bb60c492c01f4f37938d666d061b95fed3bf1561ca18fb6ed1e895821d793f4b1b391f48dcccb4203621ac70d338f54cb62b2ac2a8b4429241611c6a8f72e1c1b48a2e77f9dc98725e9ddb60f68cb7d439bbdce7bb9150e33be87dc6b55e8a1facef5a2427be414b78a67b28375698057e01aa17863fd84316afbe38669afc8d43005587e8714cda925910f0ad1a17a2bf3d475772ecb1bd2f8519d5ba255e36d10787c7703b5920c61c7f28aa901a7dc185b925e6b554fd434ea34d9fd74e0e54829076c3ecf0d9809ecf9d95fe4084a5eb65fd089b9e4c5f592344e23db7f5fecc3c1be41845ba80b9fcfeac6b6eb0571f5e01101774aea3abd68563b54422fc4dedc24c4241627a6ab9e818a7bb2d8690cf633e80f58b04e198b7d118521b1b2aef7dbef85d1f489a03d5b115d68698637bea9d4d440cb7bba58f327230fb21026635a64801b7abbdaa88d871061042acc9c600679016f49fc2df9397c27a2e65544ed8a379e21f7fa6f96312704c9978475b8e27d04a73d16cb4e4f7fc3c4a89ab900ee770e6f9ebc491a523e5be8607c277a2c2444fa0a997e58c32af6402abc1737f5f0008466061cc690e4437bb6d1b721fbf0634e16a982ef2edc3373ee217db9a2b802f336e3f4e52c0eefe7b112aff9c0a1e31a2ba8398bb674cfd11fb880d124eca47ab1fbfd3be4584454a52961cea469c828ffb40e8d22108a44968f5cdaca5788ac8b33f5a7e2b643c2742924da0e52bec8464535dd34c019b1c5e2c5f09f634a96a73ce098fab9ac9053956a72b1f49f3eefb84cbb027c076dae2bce293c17a366ef28c9ebd0f6490f73b76739353ea0538375b6c81d7c61ca1ae783d6385cf04db329b585e84cd6caf4c8931f30bf3c4c6fa38ecda39607f58f2d7e70b8f205f507b52a12bfda0e9d34763abfbf40b044a82cc1389ea7cd95cd63dd093dc6f6dd0291d1e230df9432469cc8d4b8d6a734d9ac3ed14ac467c62a5593915f5ce3bf84fb2886de4a24e7453b558d74be15d2fd64d80596382fa22a50d7b44dbc081b9fa06518161c763cf86715fd0db4c2bb7e93ecfeb84df22fde44fab91f1e2b510f66c7d93e695026a4caadf45e0d08ad220814f0eb302170fa92539dffc3eb8b3dca571d928695f767cf79884744bde9da96264ccb8916ce4154e3be8cffec82ce54dae2eec14f3891de1d45de9c89002a0bdb61eb9da1129dba66ae00ac993d55dea98c7c3ef3045c8762e1d3d3cc401e5b996c1ac57d0717e35196404997e8c63de9ff82ef28bf535d0ba069c5496ac70701d2944f90c35fcfaa42a48e7597de880a33aa0161c6ff84035ca2b39d3e642038f014b60825f16d6c900530c88665dd4905ef877c85496c1153bb40e5d439ccfbd51e9009adafd70f74d1a6dfa45a37956414291724fc15d6f4fa2749a2401408303f2a1fb17e5bce8c0acecc7a5cc7c53865526127d5125dc70d600837de7ab2bd10638a0893c00b1403ac7ee1255815dea2c3341c761cbb122523ab3d2cb37534bdd14349e2f85848c7afd4e24c63e85f8a47c6275a0143bcfa1371d97db134a049056f8d092ad62cf4d2ea8b70bedfe6524f145e342661676a52cfdf11906dc45f88ba6f8c2d63527837134e783ec53fa408bc3ffaf911da1528162f82d51175c3104b83daf3037da0808e287ae980c150c5cafb74a2709310b1b5fa4d3a7a3fa0fb39cdd67a08b5a3f5f6a634129e943bdcc8e71b4c9f72e7950be8700c21e89ba58a5e3902e133811850df4b62fb8afd840a0fd546fc7d89efefe505956023e39822065623df63688de8648ccd2a20bd12ae5ef6a6a412bc38c3fc0c661de818725f72c05e0d90d31c3ff0637f500ba714026061677164b2d94e334abd40d1b02512547997424a589105af69eaa79ea20d4a9a1c4b032b3a3373053d8f98c9c1300c81c9a2e5dbe1d79c7cbbaec45eeed10384b9c148f5ccf6d2a0ec4072bff0ef0e106585f3122d2a73559c6eaf7b0dea09d6c7ff837680bae7d8ef1d54342ae2e9237a77a0bfb4466be3d0b0832d7c9ece5ffd065a533c311022a6e0ba465bd733b7ba1ccae1acf10461a24632470695d174448b5a5b4552cd41332496feab07b629455d11c0e817067e339ad48fd5c815ea3b7472da201ec27fe04356a125c482dc0ab3f7e60fbd5c72fdd2ef22ff556487962876f337014e55a39bb499add7f01e2646aa8b896de466aa82f80747c4dfa37abd135c6c41fd845fb6cdf47e1bddbc162381c2be47435000064067f44b248e3f68dc70ee8149a151db3bf86b77feed0a552788e3920bb5a1612fda920338341880667b54a54ed31d04cbdf53a8ec5373ed4f9f2fd4c60da390260e4706a1f3594ee58bdaa4391a56172615296aaa2e302bb52e7f51b56fae85e40449b124c40817d250c3964ad7923690e07affbb84126773178aa63544b89f382286d8a2e7747e6f6da9fef27b7aed55a593caa620e4300e3961684ae55547e6d478fa86c7b7c4010f8cf0825c02e1074e4d3654ddf6f78d418d3ec662dd246b448631f2ba2ec0a526a0d54202a3e1bb3efbd3662a41f09c21488d3afd467e64fffc4e2beb98638574322a71b8063765e5095f722f54aa4497a8a3a725f7d205c72938d8607db55e8c764d6d8ea2c6e2e8152f1e0dfae7f7875563cdcd22b102375cd044455d15d0c0587fb3c312dcd9fe32f3f8a78dc4c02111992412dab3e09c4dd8aa3d3ca78e34e2799e0c3f988a4b68ff4fd3be6a9679e507dd7d1cbb78e217a763956281c0ede8ef7b9b82f421b49ce6a96ca35b715d6c44d09dc99ee46054e250230a4d13ef968448bfdcc1dcd1e61e315f06137f29eaf3415b4ed90626aaad904f5ae84dc00ef161bbcf041bf2404b74b14359db9d797a27e3e6c7c557c327e1b34f7d1b35a5d54f2fb302510f68f690abdc0f0945199cb882f12fe8497381cd9e7a8ca1e420af206c70ccc41878eb2aedc914f84a550e06bfc388609a2e855d43d2689536ce5bb62db3a3b4e97127f1583145389f88ed90d9be569f29a4789fed24ba1cb29d512b0e1633af0f0c39ea1cf2e866682ec9a5359fb198b0145e8e790c52b0ef193884ba5ec17e3b57c2bd7e2deff9116aa012bd11645f387f0fe9c8c03b42a91eca0bbeb33ee0e649c66055daadf167ece2a364a797f5fef61f72028d1354aa13aa674c1b34fe858d26b5ce166a970981abbfe230b73915791de5dc6b6809a35d11dbf5de2ea28f614869eef4ca10c5ca20bde02c9526be11e0cc495ac91a80bfa5c87dcc0b3b367972a4fa31eea58fa3cefd68c6b1b797259dbd48b952c0ccd20da83f5544fea043c0a2f9fbd911065223c6b6b6264970346da40f7c2f117c5134647c443655f9b306254db1a6de607a2a5f8c1f4c2785e0b99cde40b1f72f5d55f06e2e25896fabadb430f853876326d78d8265e735f6cc8db2a72dbf3b2830322fee661ae5d2039fe0c6930d2f0330094a6caf919522f85194a0e6c4c52da41d93e64eefaffd19ece2fa750a919d9337039b7fb7a6784c3a78c2f58152107fecb50d4ad1f0811afe9dcb7fd3e5f83564badd5d8aea97c0a8c536a49dca9ffa5c9c38a86ad47c19513f2e042773b8239adfad4aa32ef5eeff5c73dfe3251cc1e695ce0b17cc51fc1b0649e57f96e0bc5c707a3bfefc4c519299c12ae0abd37c3876e6c556cd7afc0484873be31e7b1987cee06d430226c1c86ae03ec95cb1b7c91d321930ff83f33b090d6ef213cf40fd2fcc1b262c14a93fecaddafee35c882e10ba4706e0ef52ed0df518f7c3c842d4fd8192ee3b1c337b7dce8a07c102a7d2e5a3f8c1fd50d5b26dacc25968d1f84e97ae984e2143097ff36e35fd2b61ef7bfb306fe76408f6f644ca17f4b768a1b67b32e48cdcf598a30f19760741f06bd9b3f57d4a04326441095f4091c1c81249c60e8fc8770e3e113e30e46ce82664cc83b48500b0fb6833be4c772089a2135ec93a5172d367ce221c57088a16715f95c4a7cd4f90e156714b192859944305a0685d4e28065d8b38ae84420f862e3303ee38e8522e7023b3994f57b7f619ba96e8c944a7b32a95e31a01c228a8060d9884e39302d0b258ae94878e051c5538942de45e1150b78e430a8196b5dc2a00a516e54021e85a8a73e54269acb4cce9326f318e52e91370a531bbbe4b20664a0ed8c679406f38325a7710f10bb6a6e787cbd15a95ab048e1b01db5cdf7bfaf9acd4b5a71910f93ba9a059d5018ddaeb89e9e67874c36c948588593988d7b3fe5f7973d609a5760105106cfaa359ea6c0cc1119073098f40ab4fa7deebca376c156c65d763acf42e6ea4fed96d1d0ec90de09c5e82eb7cdaf437c5fbcacc5f1ae8bc8d39e4e434734fe439f5acbce2231e5d1925a90fc2818f627c3950f094be7351369491b7f86406996171838fa3f044e3624282cb7ced33f9dfd89c1fc102619c2ab8044721673dbec5da136b17f295c405fc30731e0da0377e139bb8883f1ee38cc931783ecf7c37f07b71698f601a9b5371dd3a18f70f2f97ff9748ce0f241371d4ebfd43c407f64c9402ffc2b97f73bd3568ea99ad61f1e99138794428553bcc12a96f0f0e018712aea15340d0fc4d6de2a6e9f8d86d15729b4314aa749934c5d01b560ffcb5a6fc09b25eeb06ff7d1834f93b01feeb89f6874971fcff75bc6671ac328eaddae4013f701f0869f3ff1d009df3dc6261e246d103636f35eea24f1ab6501e2bdd249803b9283634bc5e3c3f8afeab834d207b03fac3ba296b027893eb2f3b37709f8953cfe219a9e512079abea28f69ab29189bc306ff1d8801c6137023ea5cd18f0596f7e71e5af313703cc1981b79f8ab4e0cf8a5eeaf16e5fd7b8b330db3a1db1f234b48fb6102e24f62b54ea31db0354a81e2866426811fe0891fffc773dcaa4875ce3ae81c6d68d55bda0a415a7301c9ff0df27b494f04c725f128b6cb019fba23176d84a389f6f7043e098992ecae45fcc50fa275600b2345c348641f74c039d6759c54a4724961bcfe7d6876fc8a629fd8fe6860ed9ad925740bf72836f59dead725b1a9175c52c66ef5683dc2ce8b112737a98b58d2f583b1486cc20e4a758d869f78c16b9cf314e923de11dc42355703e982870df2f89c72f645c7c686d06ec00f17728e0bd44242d8ca1e697efe3635046b55900ad98105819043e32f11ff044de28b25e7813b1842d79a6cd136433cfda4abf3e3cce909753ac8f004a6ea756324070d0511c7eb3306c4ae45c9ddebf2b9a093e92ea29884a8c602ce4c774c304867efee452f726b1be492831e7b704b68585c29e1da4697fecb67384ba42ee27e01d00fa78739056e4600ef8d80e1acca7f551a9f6127f5c821d43a33978b8324ee26b016f1e530afc5a27d598030e143dc76f8fd2c60224c1108f38807b98a6a884497d23d8a78742852ab23b118534bfe68246ac05866066c39244fa8f1400223767ac62f2b1be3fe710493f036d86c6280098a51e9c8b9ec0491139a48e28903bb7aa9416db1e51e278d74f1bc7800af3a78ab4b65555ae2aee738f7e8a396c052356536826ffe03a08478eee6c84aa86d1904a8deb4f6d545c4b11e417aebe266656ba7786475540674cbfbd8d1af92d094a1a027a989a6bb937670f30eeb4d0cab81420eafff7055504896c64a915da9b8d896e649483c1e1c6bd92aec97faa027d087a63f5782365324eb5dddad77b07414d149990d59e49a754a833cb73f70be71952523738568950ccd66628c70218c1b859246be827541e884b9bc7391da90b2b977bb948f80a2cc0e3eef83ed99996391d64fa0d6b77c7046cba5de3f79043539941e7005351fc42db6d70503f0fd01495a12000368c298a5739cc857d45085089f424567a99058f3e33a41d001de085f69cc11ecdca80a9800800a92ed14a3600c25445c5bf46f66f774b38fb0ee20c9f0e4503aae704327170cddb9e3a1bbbccaa357a29cf678c2f16bde6901eb9b391108d3b01b5ff1a6baf621b2b3c3ff72c398364394c57bea71b26e626f0fd1c5210deb86fc0e41fe465358e699cb4dfc8ac6f3cc0b5d513a285122eed0824491bf4fa8a17636d02da07afaf93d44a99e7778ba08eda0117310419b86fc0243e28d816c334d4534bdb6e58f165811df5200a9ea49fa77e453d5a2e47a0b63ec32dc8784fc19ae8ffba43efa9955d33b21819442d0a30d08d049feaa071397878e7bd75e4717f1d221c98c39613466ee730c589c96ea29080016db0bceb988dc23786a893238847925a97f37f402451362c54af7f7fde04b8bb35469cfafddd3abc14645c6527833dcd803198ed315be89c28ded61b4aa1cf19864a0bd62ccaa84e8dc6864bb66561ce77eb12683cde5bb7554a93669c9434c452840257e3d5dbb74197ff6c0e18aad910445091f893b5a0ef504431ec7d0edc7193bac3ffa42258b7d289490339afa60bab08b5e851185252cd75bd9ae10b36275116aff33746ce7aa8f0d0d23445801824e8aac51f1fd8793f2ad99592d5dfd9f57201b990dc383c95c889862f0be3dd458871d7e582284e8f00db9039f6f480c29fa0e34e6dcfdd49839cedf2f27affa9d07dcda24a29afe8c8e24c75fff6582b734d9904ff6efe5e19a760618eed7fba1d8f2f2f26aa3b98485d183b8124da21b818f85e027da693a581481757095ef591a720a157a99f16d465395b80e834f9342ad572389a3702a7e1db31265afe02ad8b04ddbc1e3a3b96895e878260fbab231d028445b625d098a6fcb1de97f49ac37cc3b407deb638c2c07afbe36d78a0874b4d401039a47abf615c0d809d5cd20225a5f517d74e073fb7afec37ae62e9f1c42a0a9affbdd1caa3006943b361df7775de54813b0abd4d49455fb0b34086f006f7a04a5986138881a9cedd25026b24cec399ced697a99c92d7a751c62e8f835a0313a51eae6d36ed95c69640f38c4b3a95d3725d64945b547807c811a50e3482a55876889edefd0cb74bd5774359d6eb950a2966ce475bca859ba8219ecf4fca10bc196480055baf7af6c9bb55c215ff330f57a798cff30194ebe4ba06a858da34d48a68bf77380870e57bf54d575b523fcdb8e012cb9542d22c7116ece2105e8d12d6e3fd720df2eb653536e672972d16b0f21df03dd80210a54a65f8545135ad196765145b957965d4978831169a2b55bc532d6243eaecbb880afe71978adada566ac68ff10a8c4b1dabfea77b18e0d5625a9332ca332fceff45a864f633cc09ba0f6297b7a798e368eaf7ec3d89043499d32852969d18d2b209377e0c2a46c6e1685d0a628819b4e5f1f71f2020725c8c06cb9aa9596f1998c0dfd170de6122fea9d43a8644a6a54a7725ced87ff220a7095cdbea2fbbe3a6b7a89fbe4027ecb8fbad57f7991f6d59cce749dd90e78d2326b2cbe6eed13e83e6282e00525470c6d5532b53fde95b500b57448ef18a972d290f69e333bbe3036d85d2cbac015484040776b36fbc4f274c4253df3bde94b96e0aaa83ce54fd13bc6aedccfc229ad816b79a28ef965839ae49ebd0de048f4dfbfaf40f12b28ba84fbd467549150d2253a068f41c891970da5ff74720e41039b403c285267311882fa2f74b9fb72939efb420ded5a8702e2d626fbfc4beaf44b0d67f3f3415d276c0711b7548e5311d0de432016253527e2c3158273828b4b6c737529f651e1a5717496e0a4286774ec55ba631a500813de2836c1ef49d64ea7c33c4a75e920ecd424a75aa794bea30b8afaf29461a402521ad747350d70b0db8429c14576d3624346913417713b2c4defbac7cad46d389cea40a5b450a0e7aa6b91c5674c41dcd71dee7cd9a9d0e12bf5111ded594a27eb0a0014d54ca234d37bab680476ff64e09f237a3c91b999525dfec00c9e74a119733b8e09620f7ff35e74041f863c2a3a414dd9e6ee4aa485390fe063629788ba4855feea4626280a042295f5bd5e81018a0973164a589768ba4e0b5c037b9a132f346fb0157a71ceeaa2f787da030c8b8c29217b8dfe6aeff3d0c5cc70baa339e4d95f56e7748616132f3a9e7adc37172bd08164838f6ef69d919c95e5d4ef7951283f371a44af0c5dc9102a69b95bbb7fc2a886cd78e27524ee74b8d055327138f180cfa592cf5b2d67be9c271b9cf03908759959031ea1395fc3f02889d732e13b09bab010737ab9af8aa0cf8d64ed835f8e16c82a1ce1b91a5a439495e8fa8c582d9c9d483ffec0162355c8faa765f6f09602413bb2004b252113094e67fddcbb0a1ea855e9a2a25782215dc1d42cdd59310c0e244600c70d245f45971b7b49b4fb19b79392652d50c7a22d6e95452f1dde12313d2cdd15a99a1b04b87fea8bcb2cf80b7302faee5c678b5671036094c1afbf9252221cc255d414ea12c1d4b0c21d847aaf4315cd2c4fdea7fae548060fe8d7527cca44622c94e48d03b87628f7eda65c431315fa7f9e0dec483035519453993375716a148a2476fbf7f0e5aff925bbd0ce16512d97469150306d8982789b15efcd2236a54cf614faf085c461f249112540b849b273135f2aba8784e0264e8ca9c3840334eac0f6236208948ca15a966a97a060958a01088e0fa0f62a31a1ee8020d1303374b9ce97a12676613129fb4002d0fc33aa953d181cc141a323446c64cb72021bd4cb5f419fd0feffc03b08850733d996f070c07da8c9237ad4a6cfb4796d4791b43207d80908844182866630846c12f08c1c485d578479a5159d499074afaa904993e10b994a4bce1dbaa80fc20fc5726271565c260637727f2e4dca6ebb615ba8c3da4263c3063d777e075c29bbf4f5047489fd098d170bc357c2649fda67e63922c227759b0d0d198212d269ea3de80a3e1ece6489ca65488e87e9bcb6a151d94ae30489de7d1332c68d235b9927305a3e8504d68503173d5e8e53f126c47a8443768f8e5ab154ba1ad8d3a23001ecea6ae424631631ed828ccbee6a4428d7b449a02ff1309880757be03486e9a24c5cbf821870df1d9d5e3ea13b855a50e48a29a0126c13a30160f1baca4a629121de2c1549c49b48136ae45d024ee06b77a8495a17b9e1b605b7339c86b1053868717740b96839b45e4178b82410780af57d75963cf80dcf1febbcbded059f293cd6b77d4b31e04aaa67333dde15b01203a847c393c22224d61c1be95b7752009cd41f17ef3b890b006eaf018dad853181e5b6c1cf4d7dec15d0dbcd4c8c92aa06e786a8965ab5232404eeb8d101af0e9a44b786f68d2ea5a2f1a62d2a35ba2149b8664b9bee262a10c27d27f0dbc03ed7e68a89f880bb43064e2daf200d042ef6faef5f30e6fd1dd0bcd08272a72098eb5bb3c4315091e0ad5fe6112653bf45bdd3149780e6586366e24335685ffd1a137352ab615e58fa491411053f67548526d02c73b042a64960a8d96710845ba321575c934f868a81f69d297d1bf07dcb47603bf58936197010d014630d3167e2e62a172baa8e734bbade8dc1659002a037b6a223c28ecbf11fd63b45743371290280372081c111d5b37800a57c80da8817b6278e987da04dff2735fc8f09c4aa6a3e7cd96d0432f34c5a9200f056623e275872ff2090f231c8a2b8e91043fa7bf6243487f0141c4911ffbf0487e08676129cb2f6f7a5e3b07c91b815962d81f215a081d1a5a4ba316391af1302835808f65e27d73bc09be5cd60f58413c0775a4d4367c7ac7e29091abeaab300534390118e46d4338f1100d9f36bff4202c0091c3a38b029541417133446e48f15076c24d71f56ff6abcde3ea493a61e700811e528ecdfadecc6ec410c3425e01c27dcbdcec304af3a248868c8dc09382aecdf1ebe635b9abfd3d69c1e943d7a13cf78f9cd31f5ad06615fd850b144c9afcf05bcd217a2f88d59c448e9712930a79135811d841675775613ae06cbdf9db2839587f54bfc636512bc20597521f818cd37e156974aac122db39b32c44adcc4fb1c5a471261db2e93564e64d1db241081775da9ceaa5437fdf9f3aec9a7edd3724567f7be8586ed89ab0f2b7147646d97f13e1713434c41a8554631c252f456ba085792904699152d9142d0d5705fd670bc165cad21bae4246ed04063c3c0877e24ac949dc3fe21a98bab90da35eabd4dcb151e45d40317c9f0f467c38cc4e87c0caf1373eceffb5d62ee78445714465df8b8fef5c0b9d50dc2d34653cb2a71c0af9141569ce2ab1cd0fabe4cde8f4e56cee5c501806cb3d52312807ff60e6d2ffbe5518cf1dc6d00e8a39ec6f8bcd48e32161185d3accf282d2437487a74d50e96ab866e16916dbd9b8ac1dafe771855e5f61c2437ee93d12a9cafe16e863c47906d778e50fd62e7ac433077cf7051a8cef865065e0e53b770afd3864571774dcf442e875d4f136f2f0bfdbef0b405b9ede7355ee742765fcd802ef5b4beb2f983cf030b326f87846ca7f5561f3bc901e343a7fac2454a2d71ef4d79e493bae88d85c7b72c1b969735105be2ea5038ee69c016644b8c277de526ce03b18ec7f833577d52984adb3755359bfa699d6ebe37a0b66d3c2c300e526f2517dfbc60cadafd2ff4e2ea07c5fda9284a895a77d450c5b0448fdd527fd134b42b44580adf8cbc3d44763722581feb5f66d4159b9a7063889d392c50b2086d94c8e2e2daafbfc5dd36f40feeb440c8fc18630cb242096b37bd455d48abdb351efb72c8bec764f12b1d4f38a3221d95fb80e6b4ba78d1647d3c683056c05020e3fbc20217b1b1a0d171c8a04c9c7c896ad807b324f65d53ce92c80225a24eb9952d3c6243d9a4f6ef3d34cdc8a59262e84978fd7eeeed0ee995c35f55939c1008c7210be5c92c98bd8a9280a3fd8152965d43db817abf13b6ae39126b665629d74f07df538e794a3e97c7a1b15e4f09b5ceb2be40dd5a2bc976c9db252a39a68a361c902bb5f6d8b74ad8e162aaf903125b7f5cc93913c4dc65d59fc5d3d40c35514f04490bb6939b331d44cdc2d0151d3cf240105721d49d79a408a7cdb9b1a0c7907f952fbf4da1a530f9071b916d1a9a9db89e6f305eea6b37e4f864c0ec5fd257e760795dfe7004c3cf290fdd240d79049dc8c9330b3c1cee749827e16ca3239ccef69981226443c37053de1d0264ed70e4050c051706d68f7eb631385dbbe9e6d4b3c0e339fa44214e85e5b93c7e83cd8a144ffdaf45673b86ffdaf835021cd053d4b6ba70aee6788c7cbaa1a723cdc03a285a3009c35b6dcd1e3352182ad08bdcbc29016a55d777b973a97359bf94278fa7109fedfe8681807c8cdbab03770a90dab52b3bae3480715ce6badad6b3acec7e3b72ce028fe7e81385bc069878dde806371e3430ba97eada8f584a355bfcfcd912466118aa2a7a93a45632d7c78d9c031dfd2679fc3bb9ecd2a0ff77014f8b301fea4935dd5786c7cd34031ac96028a0569a6bb7f1840f4cce70661045a0b3a88beb3d4d86faba853b2f671c5493a8ad1969d666dfed00f0b9c3b85fa5e15f65f13b7ef5862e1d3ec3925f56f95880bac5f53b6e7424193206ed5beb63696aaf78f921a592406f13bffb01ca73dd9a973a0f6b16b999b696cd14669a2187d488d8d3c05c4a241c1920ad9ee90c92050c82fb5f76ce3f1a91c94e1ff464b401600fc52dde61691c5f33b0638d45478e5f089093676278990d7a59e09203067baa71b3166674cd3c9d5e4e06174da3d75a4f47e090b550e08ded4deb6f0e872da92e585b92d61f87c9d12027063cfd88762faf89e7703649108136a71bdb04ce231b4e5074cdc4d037c027cda73052e2d9c52a259fa7d5a3e2ef80d45af9957f08c523100233463f73b053b7eb96801c85ccd9a701c151fcd9b4b15be8278484ef41c6fbc1506e419082344071a4e1247dfaf009bbe18b148d7c784b240dd57ed907a541307cf789fc833e4aaa45ed470c9d20487b27df3fcca96b8c95de2ec133fb5f5282560d17027835a7c21a928927cd2c4745038b39e821953a5f19f4789070f792b8ec522f260111ead90194405474c8956c4e1509bc156528c0551f318d57627a43dccb9594cdd2e84eaaaaf4151797237a2af37bc55df3eb168cc292fbe3cd48944ad7e3dcea4f32464b24269b5114e82b3cab69e782bb686caffffa3765d33b931f2fc0b82caaadd5c5455c74c173ebc452cd7fcf81ad884c9ea01c58600c323bf54246848ac964624961f7812e0511b7e4872d3519b2d6aca018b7a82aa8d3331952a6472d5ab0a0e0979ba14d3f2674e1ee2e952a26ace287a5d16f7ba5fd51c8905e22e30621501c39d9828251a68c85770ac0fe076216585374e36fdb3eac76af15051eb9edfc81a395f5b7032e8c549ed8643328cee65b76cfa0813cbf895de8ef59a2faf2d29c1891bd3b3412b28e8fd79be2ae6b7dc9d6d01805af2eaca5ebec2753508526e89906d17d6f3f48473bb096b3f5e0d5fc5c6fe406a19c183ea6ba40794d93a9d7580185e7ec1f1130ae4eb8fa2e58773ef5642280867a1f67907429bb2df1207608941fd35cb4e92a6a671d8ff008690a2395b6423589e07a89486f92223ed5d357b2752357bc6e988bc20363b94eb9a625d70b7e7f7433226aadfaf3e34e35caff0d2d61e002574645e7d55ad4a81ca446f1f51148adf278078a34b73ec63b83473a1092c520fd7f25b43eca6c645a2b8af070ef28c02295d21280c797d6fa5f29f3aa87734f6fb333bb28c8decc98ed58314d945e77b4177f1558b1ac1f9b51ebdc4ea71169c80070812e9d89e7d32f5eac529c11dc4dfead32145f49bc4d8c3aece0190f67cdb2c84913942af189099213a60980178a3f288f5d71ebee91eb394a4070cbf7274de60e01e053305b2f6cf3dbed34174c11deffbe5de6a75ea2e32e376c2f51ae559a572fd3c3b57911b5264371f50bb5008c6e6648d87b89dbed2acc258c7a76c673e375b44111b34732c6b860ecdae9148e88c5b81931275cb398299211103a6d82c73c37d265099daa337c89c1746f04ff8c3484aa7c5a3fc781927079a5cd8d074f4e5176fe06928375a8d395265a024510103d42b19526a4f836c68a9ce5068017b8de2d7edebc8d8e468405664940c8618e9739dd9eb4e7eaf1004b55471f23a3ca17a74b7d3c214f76ed11f0923ad8da476c889b52e14a3319f4c88bc75a3c20ac9a783ae095e3a677781ca8a68fd62a2a125e2b8f57af304a3988bce6fbc3ef0ec0742be8ccc2cce5c263ee4a74d3da876e5d9066c47178565a09271371b7cb607de4965ad9796ed05608994002da5671dcea3010d50323111611830505f746259a783d0b6138bdfd680c8eb13a5c8da82cebb2dd558a95c28d28ec9992fee4ad52d83fc10a29de90723d5360d4b3a3952af125ee4a57e563c2856e457f553438713b5da9c6c392c90ba39b046985ce04d3539ac94a37453d52b8c56357655874a2f4260abdf07afec502401fae68934aa5035409b9a688c7fa93175638f71556537c4c7023f4af08322046146fa7dc130a7aa5f08d94f1a067f2327d329f32f7baf8e45ca58834d22a0f5fb9c08fb054ca7a42811b295ddc4b77326616fedd798228c0a56ce5c3ad952a4091e08e92de1b19fb2ca61207836b511b1334151714ea0530d86f0f0621d777d23e3c1471f8af7a38227647abd30b2576d1a3f2738ae2eb990283fe5d574d94f0805ec5c58f28f5f272ba457c817f4a499c9e13f148dff52b959889db6955a5a55360d6bb6bf5746914e94ff40a0a3215591185b752b8b0e70e3ad23fa400cff64c5e927e420056e927948a1e15ff2a3140f1bb5fad305c293be92b3ea2dc1b457a4df136ca3d3ce825fe98c2c57a4c104bdfee576c2c11bb63e5c45228d14bf3a0cc8c9f0e1468d957d91af96487aeea96d149e976caf6e4c1885ee233ef2e0a7836ba9455a2ddd84a2033a3508e9ec4969604287b15cacf7cde859b8201ddaed85e27af43bce205ae77c658183a101033b901dc869b6eea4fe8cc3a9b9d18f72c3a059386ed255a01c76acb5aa676de86beec53093e2fadb426912a9f75d7e7500b47207bff230538164718e71a81741cccc6c666898b13914884a9365c253130def7ad95b070eeaff0afe6727225b20ec772a5de8f9406c146f809b3b1b1e1373190a65242f6bee0d7f66c0484edbc6efeda1f97db7676b96d37ffdcb6f3a3dd6c5b0fcfce76ee6b1abf8992dc48dad28442edeee6f29e4e3ea8b342dbf34e2f0f0a6dc23de3dabe554afbd559eadb6cb5e5e6f9df59bcf94b8aeabd53ad3fcfb69beb3dabd4d77bb96d17694524e432199d6b2f1785cf3cb10cfff976bbcdc9e8a85dadd1fe95cea9761919118e00cbc6686d9fe1d9aa5e14968d58e2f6ffd53e4a46b5af3fa7ebdba6c2541bf7dbff479613fdec1c3efbb79f7d15ebeb6022150a8542b758eb6c5f9b7a8bdcf4decbb6ff8fac4ff12d82d1e1dc57735afbd7a11675458814dd8aa48ace145df50e37e1dc6fffdf1337d18c4468d03afb73bb9ef3fa5fad56faefdf592cfee96d603f94e3f6d4d018b8891b32d1e1a621b80e37dd4279702470b350de0bc1c14dbc5015dc249487101242584204f0416e3adc1cd4036e46e1e62032b8292852ef7053d0980e3705c9e81d6e0a920b14c845a77637d016a04070131092dee12620b5dea3ddfd63e74f21dcfc3343ff14abddf533c68f0cb8e9470f6efaeef649d47b87f6b90237e7eef691eb530537f918f2c97bef7c28f40e37a7ecec49b44745ef44bb6ecedecdd563c2d583951a20606a408028a80103060c8062517825b60ee8e4e776432bc2fdb262ebbd7705889405304485e87678e63b52eea8dd61d2f19b28138b68786ddf6171474a9c3ffcb1f614876b1a10f679d30f08fdf35ffbe77f1ffeb1dbbcd9b4a9f51c005f3a3ecfed3da97779f6ee67ad1e5a077aad8db5abf74c67f38fcb01a077fc6e4a5badfeed6d7be7f39fbf1c0e9d78f576b61d5e3d67dbd97a710cc5c911e8eb886851f8fe1efebbfdb53aadbf148680c31188f89cfbeaa78f80d7dba62691329afcffbcd5adce1e01613634d80808df769c87897fb8a76b7b2b3d9b83a7b29ca9eb7d7be7ff79f57633006e2c74fc265a7d5effb9fed3d97af7dccbffdf94d69ba3b04405b6e9d17bf7769b99d846ad5fb335bcb676cdd66c549c19d37b67c3a2c34d293536701d0e8e0d01a9db9a433b4feab806aef7be66c0f3e3598f0798e7d9e541edfb0b426bbbd60bcc0fd7d41caa59aa21b3efbf2150ef4ed3d170e99df8ef5f85f179f676301a0a0be63f07e7b6dd36ff73efffcfe7f86cb5e5da6e3b0ec7ffa6361deee97ceee1f08f7f3a900e9feb3fd380345cdf1d1ef5aebeb36e6fd89fe75d69bc2ec73d5d8e7b3a3cc7b3b7dedfd4a67b6bfb7f5e2f502fc73d1df5ae3ab5b1ce365b8fefaff64f1cfbf3bc1cf7747815f7721fdfd38be212b5450c053154bdc771c14c7bc7fa6bb319c644dffebfd96a37c3743085bccc44acbfa6dd76be9d7abf4f27a78777281dbdf76b77bf58ffd5ce83fad120fe0d4b28c2553c8b75bf9cf93f3beaf84d947a7950d3309d534e1743b80977997b97b52e5fbdf7332e857aff563f3f5b8fcb98de7b5e10abd55f71ffad6f5c027b879b760e7339f60e37c9b624ea1dbaa5ca1642b8b925dae126d91632b84956498b1d2d45b498e81d6e6ac12373b58869a193e50adc5c91a5dae166166596adde3b963e580c7b879b3019166ca01e16b5dee1264c864541a5dee1e6153586339dbd92f70e37c9ae54b87206b5f2c94aa12e851d6e5a01ecd0e1a695b30e3761b2fce75981c9aac8e91d86925579522507dcc476a8e92acb2a631d6e56f9e3a6772a4fa8f8e870138d4a022a04a664ea76f586e1297fa6cc99b2a39791c1300e08e775d6befba474921247ca974e2c2383cbc860dc6e80941caa40941f520ef4de719a4d2daffff5fb792cd6ff6a94d480a830ab8702abeabd4fea7d0af5f505691cda3b94175457efd47fd77bb54dcfdfd93b1497d5f70eb535a5f3dfd63b94d654fdff6cbd373cdbceafa69f77967783c29a7fef50575056bdfbd574ef50ef14ee730f47ace27fb6dad3cf178dc64bd801959a95c2fef6783ef5f112ce716fa5da3f0fd19339fbfe82f79f63ac7042677bbd66e1e6edbf80e3f39fe736dbae6dce495cef9d58dbd3cf079fe5b8d7da763faf6bc36bbace6efe82d3dbfe7f5e2fd0d7e539b7ff826cdcc3677aef4f6b3a6f6e9ecb7f15f77278f5a7c3add636dc7f2b96bef56cbdf9fb79fcfeb415fbe3f2c06f47c4add2ac3dc35f8ac3feefb671bb3a85efacad0126e27836f7b7c762bd99996d57e12c159b85ed1c0ec9c7912038c2a5fb2a8cbf59adfe8d787f6c6dcf1fedfe78b6ee4d0a2f684066e4aae33771fbffa0563fb569dc708bd0e847624c8cd1ac1e0d0dcd4a85ded06ba11e719ebdb5adf02cb0b07bb4fb1361430426f284088dde6136186ef62997c854ef1d41ef777779c01922a6f74ec46f620cd86c0a112424509f633e5befd7c135b6409a116410ec81839036bde39f86cb6d6b1ed143d4d6ea65e7db6bffa65eea57d3354da57ef8d3562b7d856b1abeb1811f3eabb3350d4b897b82c02da0fb40b95055bd777f67bd0b8409f1fbd5deaefde668b83b9a00a9f6686e5bf3a240e83adc44d1dd1f547ec4f831d8f19b98d66ce2b54de3f5cf03ff8f68879bf3c7f73ab93efcf4de7d30717d187d34e8bd13613b87cff4a6b4dbf463f3f2dbaebb6fe7df9d530f9a63bd769e3df4f6df3ae58175fdf91c140a37ff6c4ffdbfeb6cfde1a6bfb3dedf59bf23da7b5f11641004b2352805bd7728a804506cbdf710ddc5d1a8f70e17b13ec7af748edfc4294e5fd3a6387dcd3188ff2c8ed3d5e9cfb7397d7b6cdad59e12a5d96a4efba5a535716c9e57e1dc636109f3afff9d05a4f1fa53ff0c7b3747d4cd117507f97477f0b0bb835eba3bd8a4bb8341ba3b28a3bb8327ba3b387777104377072f747790adbb83c4ee0e727577704077451ceaae883cdd15c1a6bb22ae74574492ee8af0d15d112bba2be277570486ee8a6076574483ee8aa8eaae8800dd1584d45d413cdd1504d35d4126dd15f4d15dc119dd151cecae6088ee0a46bb2b88d75dc109dd1564d05dc103dd1584eaeeb65277b780babb85d3dded96ee6e8b7477dba2bb5bbbbb5b0eddddda7577fbd6ddadb1bbdb07dddd6a75773bd57bbf4280e08080aab7f7045957bd5f651dd8c2a23a0066006bab7704550a26d180f49cc1b581a203d233f4cfb4364e1ea5d4a34f33cac9e9329e934c25759e8f472adafc03692a1a5e46a6fdbae3264cf6ba414210973d8254e868a12e84a4d915a35cb5d4c131c89393279e6ca811b13fc7e1dadff0d69a4718897011a11b62139fe96c0fc2196acf841208af7aef67cdedff83926dff1fb446a2deac01d7b852238bdf896818d2c83b8d65ef1d6acf3414ccc83463ce8c2d507b9ee1a2f719783396bd37e719543264c890512763d97b6fce7f9e3df44ba1760e85dab30caa184262a888f1a1f74ee6ebf2f29f9797ffbc3cfb96ffbcfce7696dbc999eb379f9cfcb7f5e7d67ddf29f97ffbcfce7e178f9cfc3f39f97ffbcfce7e53f2fff79f9cfcb7f9e5d7de63f2fff7979f9cfcb7f5efef3feac5937fccf9a756b71edbdf7bcfe802d98bdf75e7f386a86c595055627c21ef644dc6373aa0dffdb33c019c0b887737fc57f558ff86703ea59adfe6dc5a315897a7ffe4d6dbaec5b16b7dffea6365d8edb531c6e6e0f6ed3da7879fda7f7c7ead574dd9fd783c3f3f0fe6d4ff1b736184febe9f23eddafb3771ef8e1e6f6e0405f07dbd5a75d7dde1f0ff7bbbd5e5b1d3ed7b65b1b5cd3351de8eb027ddd9d75abefacdb6dc7ddb53d9ff05ddbb3a66bba0e86037d1d110ef475305c9dfaaf07e3369dfba7b3ada6ebe82b713bc5dfe035b80c3ec39bdf8acde6b01f64434383cb72bb4a06e3b5d5c1b0f3fff93aad6dfd6e57dbae6d9eb3ed72db7ee27373f51c6e3f6f3b4e6bd3d9b65e7d8797db767776f84c67ad1e7086db0bd4b3b1d76ff53f6fa6f7667a2fe7b3ed7e9ead97df36dec76ef5ea3a89c0a9de7b19ac06cf7436d070a6b381257aeff01a5c06fb359b6c7a6acda65d5e601fbc06e33711b7d27916ea46a20e37f1fac04de79b9bb73170d3c93785af4602379d79da1ccebcda45879b709f7aef9eb451b859f65c2383e7f43adcd4d3cac14d403dad5a879b6fda2fedaf0139c14d1c3050879b3814d04e879b7980637aef306061879bde0d50463d0798e7b8a703ccd07b879b8013a218495d650d3b7e1343c0126a1a3ad3d92c5e76d9e126d4cea1339d85e2373ed3d9c21c473ed8e1e6200c43e76b35a76fef67d3e513f22f180f1e070f831fc1097115f8b6c34d186e9e9dc14de8594d5fe1b3dea78c7883dee77b4fef1d9fe92c14af6fecda02f8d6ebf9c7efe6fd05cd3f7e4f75b879fff9be0b0128c46173a6b3d0dcb69f217884200c919f35cfa0f6dc7bef21ea422c9b6721be7a879bda5f4373baa6434c75b839d37ed84670130e760cdc0cf4570370135bd87bc7aac0da586b879b7f30dcacb1721df753ec036c552f6b92d9cf4870d386631fda5b7aaf6dd8c66107f6decf60a83ddb7d502b9dd759286edb7528c24e6570198c06c730196c576ff8cc0cb6ab371a4c56db78f6cea1f3bfb3d79fa3a9bff40e37e1268e24ebac65f46ea0794cd2cec06ea0f93677034dbd1aaefe32d084ead504ba81e6b351d9f5b66bb23cdd40b3ef4c37d0ccbb32c1af2e0c346dbbab85debb81e61f11ffb96d67cd622dc46fdbbead35641830d0ac5d2bd65ab566e87019dc8cb31ea1f66c4d608daad4e1e60d9d7ec0bf2dbdf7e6df863f0c6f7fc7bf07eedf15dc849b6770f39aada1b83f839b674028994102840dc283c00042ae77b86900fae7a7d3ff670086cfe0e6f403423f0e4d22655fc5a1566b967a4c66a106e033b869c000080601ba0b020cc454ef70190c37673a4b46179a674fcba9b33796fe4053c06fa24b67d154f414a7426570f3ac49563dac5601abd1aa5c9542ef7013869bd01cf7d66a55eff8f5fe39d4f523e4b7c7f53bec11e026dcf4abe95a6bfb0571fd72c0cdedffb3fed9af0a376bdadf33d4cea1b05fd3afd87b8f003767b84943fd065003a0f6e91d6ec24db879db54ea15b8599ddad88fdf44ea1935abf70ec34deccf716817f0617cc1dee12651fbede024844422a1b93fc24730bb716734d1285a4dc338201c8dc268666670754adfde3afd330cd7d802a9f0c7a09fdb8075d69eb3f8fd6dfbaeed400f85e61f87a6e1d0bcfe8079fd01f1db9eb3b83cd3365fb36920fed339f84c0dbfed399b57a7c776a676db7156ab7f7b6b360d349bf9347b43a1f59f67ba0aa5b11e8ac59b4daaf5e3db66b3d9d4e3dbdbfb44a4fdd9ff54388aa5f328d586cfa2d97ceaa36dff53a156ff3f15fa3f954dedffb8b94f8562e99c2dcfc0d9cf6dc07fe3c167f35cb339cfe53f8fcd0e3e935373ba7a4cf44cd4ffaad7a607e3745ea0de7b5e91de3b841aa7d5026db73cbcde3b1bfe94d37b9fe9ec99de737bfbe71a3ed35928940d3e9be7b6da5f43a13834d05fa176769ae3f6145adfd99abedad5bba6afffcfe7f59d95426bfa0ab5fd7d7f2c140ff45728f4e7fe8606e6ff8fbe4203fd153aff407f8542efcadcb920e29f9efa3bdb46bb7708ecdc74dccecfaed83bb14e4f5d8f4ef4f8b74a3574b7ce015da0de89361edd201d1cf182a10b577a275eb0bb70d57b275a08632107f1cf4283b9467d0ec51c5dcfe51ac96de9bd13e5a07263c40a542a0476b8b9bd0a63719c8876f5aede714ae20689715fce4444e7d609d76bfa0a2708ceb07722dc1bdc5427be0171dfa2bd13dfa0dce4f44ebc6b7bbad16ec5de896e1b993613bd13dbbc2814ea9d48414b61d93b71429e094888f9974e704eb8ea9d58df59291b1536179d6d6c2dcf5a21716da9c689e8aaf1508bf64e54eb12dc1025d81228f44e749b7a9a86fdaebb4db7e654ef9dc82cc2a499c7de89cb334b13c425ac6cd47b276ee8ae52d0551e891fba9b0493d491ccd089b64dc4dd64f1ec6ae7b5afe2257bef10ee90637a27da5a3d6420928c86f39f7f5cef9888f83f0e1f76f76877c4e7d91fd5ba6bd464b4d33b6efcc632b992edb7ff4f6ee1ca50b9e8f3c06fe766b95d9be161bd362f3acf3e1aadb15e9b17238adfc481f9c7ff13eddfd8288ce3f98f936ebc35cf6dc53ac529bdf766532fbaf5754513c50fd8a29b66a96f3d9c36a6afb013d344e07f9b46412fadcbae89917ad7231e1265b844245a252cfe6fdcaee1f96fc972d8fbd534a0ff56f8cc45a38366221a2ddb4ac9e0f99755715f254393239e656a3bebd17b2762f19cbe6dbced6cd9effb4ba8ed999ee1b6b32ae20ccb81231076d88d20d789519c6a7b3702dc2310b8e2749d6b368678e69a159a617befae199e5902fbcad66cb2b1cda9916d314126477674c90e40c802c10504d940772190b90f023d90f1a0efc19b03470eb0f4de3174d7c1ec3a808be2697f0d47eda268b3d5b68323c07875d37a8bfb7ddc9aa5fedb4fb7751bd1c35b94c3afe653dacfb6faf9da5fa3687003221ec5037d5db4868bfebce895cef1bf874367eb95fe53ba86bf1b0cd5fe3a0aa311b17415823bb6c61d43e18e45ddb10d635378a0bfe2e191dd11e76f46ac2352edf9f67eff86cf68f80cae410db7c1b581b201ec36785d06891818f6dea351bc288cd6fc80c4bae882310b682c08b160cc8d0b9b81ff5cf8ca15fbe28ae1620bdcb0496e58a11b76012c53ef9d188d1ee1b535b88cacd9a4dad02f48e3d1e8ecff1df53420fc06c3d51c9bade67e1188333402f17f8f5b3d7ea56f6c1607f1efda967a1b10a7e7fa53e788d4adf4b6cf701ad0b6be815da7ba0b4683617d99f92a74bfe65ef73fd557a779fdb3213e119e897eebb77e76bdb078057a5570bdaadcae3c5d50ba7a74c9754170b914954565788d4d8ef1a6f5cf5aa9d3dadf523f34343c5a1785d160b86cfe68ff53d1c86ada2d2adf361c2d8bf1e008701d1c0176c366e13c8b856fbb2c2ac36a30191c8dfeaa16c6895f0adbb9769edbe6fae37febd97c1df8e77a0eaf2eafffd5ae6b36f5b476bdd5c7dd72b3a5dd8ae06e19c8e26a495dad584b2b4b8d9b75c3cd5acb2246f38f43ef6faded28fcff7cde6ceecd345c74fa677b4a0667af73ed6318ad0cb7fa68fef3eabb28191c2d8bc9eefcbff1acaf06fedb6b3dfcdd60e2f783731a5a0785711a86fedab6837a88d645396ca5feece3be8afd737056d46cb567ab8d25a7f7aefd3516dc1c16b67f3f7f7f402fc52a0662d5f9551e8fc5fa0bee55e19589debb168771fa0a8f4ab5f3ec14dee674edafe0de7bb78a845badfe5b6db1eabbc3b3caf0b74d675d86b938f7cdd4fbfbf6a8e9eb3bdd4eff0ddfb53ddfadeeba72dc1ebdbbd8407f7529f4de896eefbd6a4cef3dff781521d1af2aea5651e8ddae3edd2ab8f71ea9bb0ae4f4de3b4e7b05850aaabe8ab57a0545626d4f155411dd046e0217bdf704196afa9aa0d8bb8dbd7e0499885858ebad5317c198ec8d4560c24520d7bba78ae4524df978a0bf6a75978ad0a59273a9e03b2b0dd43bd1cd4e61f7800bed01b9dee9ebecd3b652d83d00772284ee1a88d489c4ee1ad8d2fbfdb1ae0117bd770378449c9ed30c90f5fe6f38c7eda95b205227d6f4d52d3026d05fdd02263a6ee71628e6d5745dedf3ac3451ef9dabbb04ecda2510b8fd5297801ced1290c203c6f4debb3d5f617b1e40d87b6dbb032874574020ef0a70912c3b02e2cd91c147aaed0a58ca69d0fcd77f4a6b733740a6def1e6dc00267a97d2f714976671f8b6cf7aef1cba1b60ab8af5b79fcfffb5b6b7ba3b95c99d92d3bb3b45d8fb540457cae77e9eb321de5ca931bdf734abee4a1576578aac6f6bef4af5debb9d1bc5a4f74ebcedda8d72a30804d277ee5d2839560f78d7b40b853785dd5edbb9db7bb8bddafb36a7ebecf54affdaf7defd7ad5a74c9df83f4de9d63ca7adb06ddb36884f380dcd3ffea9aa7b92d37bf734a5774f3d6e4f5122de2952ffb71d522a5544090ed039a303445659d70bafd88b0733f01c1a6ab25dce00b1e223547dc2eaf251098526b52639ba08c2fbf282c5b6c7530871444b8c3a9a14f940d26051170d424d8434a892d48799e68ee0ac50958f2e2001645083e8af01613341404dc19518ef96e99126416c33a212c0062e58714162e2d062b645038da29b0d26547ef0542153707a0a5a5166fa5306a40001034baedc0a6df2e38a06c0847505aa8d0504785c79439a14006d1990f35910c5f64588448cc98ac3395a4a0a3c5d456e6f8e5c7963b5264f088a286ad0d4b910af19c30243dff2c24405a10d5b2d72609ed48a4b0c51085b7443c957bce5a0cbc188366a73fe105a3d812c9933448a56b1af2622402a42d71604371b69046ce8c3e96a214e4d0a4b82c262aba4b285a41c125c04c9b39d296978ff18420dba72895312a474c005405b5b5a3bfaa238488f5ee0a0e8f222ecce1f3030118dca4c6a925954a6011d812a434792f78a6bc2337391b9f02df0026bf969a145404ea6ba354425fc9e38294b046647d1579b00c9e80c0f6ddb91e38ad4818506ae27426d9c3274e0e3c2d35724307d767c592482bc22eafcfd5b4372d608ca02465f616f5a5d165c2d0108102c21e40141756f287a84ad2d44991e92bd6d2172338185090908aa177e3470ea22e7015a60057b6567a8a861a8374544a23089ee28ea4be49605de7ada5c795417428b8105863b92b0f03b3508d98c94057a0380034487e8a074c97163cced2c5cd10065c1f68000e5a92a53a2906ccaa3295a0ae1ac10128111a22b4a622e643f661d0cbd88823005784850571a22101372e495c218b94d4193a3d04795874846fcbeacd9c29157208a88c142d1abacb50dd4934d1fb64070d3d5d5012c90eb869a4e65b202485263317021a5b13b6298b69de4205bcc7c0a434cb426edd101ab000cd92614a12458e28e1f0c23c05452b3a5a9af2e0e0cac0631373992f8410ba1494c0965c1db95caf613c3258e5e201cad150a3747366310d014e2b1b320479b1306b6a625da384189b51440b0a64c93275f1736087d45e42131d60683843c7084a6422b045efe66f8d9ddf9f4a2216b63f7654a6b8e591c960c031961f000de9e18fc05004393e7a192f497f668869e02304f02a47981d00308640dea64694befc51d18848268b9bdc9b1f90202e6fe401bd2a1054901f6dae9e274a48c219c4b535a50a6d49137c219423f8734184269284303480a23715959b060c18246d2829a6294dd661879bee629830d5a1a7d95f99ae0894e272834dc30602c0556a164431386259fdefec8d86611aff71d405300b54adc4f5777d2787875211047068f2039b06aec20b1e780974157cf7942cb1f327d666cc32890abb047c46ac237c8c19d3b6f4a1e341c5d1accb65e56863fa4d025482424c11253204859adaa46170241a9f10283948cd08f463a9aa46141f890270fa5bf245dac7c9d58e2c54ab02b44b28682625c8d322821236e933c1aaa5894e1a2499c716df93c725e30006179c5e42662c68f16b13c4f457784b8a122808b4e8d0a5700184304c70f099d73829f0a550d7cc89813405925a8a791f2a4c222baabb3129102c4a900808b42260e3bb80a01e252126924a0c70303501bc95b44e1072db1152d1011604b938129ab4241978bac102b2b79a4ffc0cf96ae26f70bac8e39145b85cf3312581d0f16e4c589e0a60b10426187542d43a8bad84d9894b92446911b1fab350a7219359556b029b0dda394b466cd1615e1047c2b69895a8c3c78681ea949085370e50e2f43ed1e880267c644b7b1f0ae9143e586128ecd923a0a9fba8c8825d01a84f373090d56270cb00d36411694d001e3c087950372184c52c542c387103c16af06ac37d5359ad010f9105b38eaa99705890385329cb45bcf7c83a71a7548d1d713b98a92171d7d5c5cb4d5b949151e71177db2d0636f8daf8e253239736e70d4ac2f068c0d840c431793f0290e370030e1f58958884680343c4fd214a0b9b53000b92546d8e531b376884d279a8f1f8c2c6eb000753042a31282e8882f679e6dd7b984579fb18d4ca56a8b900896a8730ec10c71b34c7cf814c5092e95b08dbe8101a6c0ea00038af9b157d564d3b725af909db12e2b64396c138bf4e6061d29a9fe2970562545530734536ee4501e5db9808281e6cb151f4c56307d6971f5d03e9c4039d246e87a2d15562254e8cedd997e85a26ac752d5053d2a7c24b00348da63826ace89155d571fe21812f334a0a6c6005705107428ca05980250da8319db3943d5ab011ba099c4ec8cdd092052114f900f65b80a14315db22ac44340873b612cc87861de2509f323802d3f5fd4543aa084ec361b70a406b16d8b1856044d9212d4a842e3ca0b19b7263a0facae1206934db8e2f8280280494d9f0619b6e4607080cb8bbe95187120cba5ce19316c83e124acea520fdac12e8fac0226e69bc950abce37387af1318592a6b300546865c1030673b1422d320156c414c87400864446a12a2235511f1e1b81e4ab50bf2cf2242043d8c7973866c169db6b94448b8cc61bc292d51610d4d789394ffce07c4da2c223c19d21031f99a991b8ac3632af56144ff400ad32b9fce3fc7ba3882b4bc88f3052737b01a4d460dd4943660afd72425d5578398983dbc2872e30c417a5fb042e74dbb0f0b788d20b056d2a6019c9c252045603294480a1297f7961b6f81cf002329d29494dc911f96241068588fbc66d0b13d3123408ca34441d3dd6147f30968dafc4cf1e0e19bed8c9c55183c499a8cb92982a1d7893888c3c1f292e33faa4557a7880f0f0e4e1210f8c6c505e26be21580a3ac1555ab2765c5208ef4a74c9eaeccae3b4239e4375628e9f1a828e1c07f055e523fc0062b0644e03b3ad23015dc58e2b1b42f6f8186140f36769ab5ac144122f5c59a69c182a79904c33e9030a0dd13866556e99454f10e119a4d627c2d5f63921e22acb98844fd09b3d6dae1a0000a04e113d5c5e636818fe68c340426eca850b7230eec4a453aaf48d9993c4cf9e3e1fc2ba0c095909896203e64931c00993355de8c8314cb0b5a2eb801e8d236b743c3995b55081c70d5312a5515685ce4b004bbc181551a9ee5841e094618f02dbd22013007a08408702b4246db2ab705c98c4a78e010a34faba0c007b2468c1ce96304837d4b89143c51193172f16aafa0c25ab3d2f5f0245d9c7177c5ad23b188069792298196e491d807af204ac49e86ae5050ed2525f98d49b32593a5e383863677b51415cf8ee32511f76615977a0a0284170e98260a8e0ce24a64892af1828b65638fda2547858dd65810423818b27becbbb100d527bb2ba74f6b2cc65d1753a4216b6340323cc80ac38460df878055a3b30c9419a4ad7ab0b3e799234a2e35a1f40d0f605c97066cebef0f3bb2285a5268dca1e5a8adad2881b8cc8b41599f25a6e12e224b090852a7d3029a172a380d39512e20623068d2852d8741d123739db60c1a9c08146bc3770f85244b0d9458bad4b47122d990413449af4644d9e35552108aa84b4b8b1896e8e8fc7930f10460892925d9d51a4ed22c6c1706d07928a070d5daa705d6a00009325320fba4d66c0ae4c29835631effc17d5124308c1344c227cc4c5e74aee8227466588b0388e4db19165854fae0d1f3c29b152c18f0c5a9f185139013fb12997c2e045b9daa86cf3a098710d98b082f32339494dc19a048cecd0f958a48470ab0c853351da5879792a59031df3f18e6193e458da33228f13087822b44c2181a4cdaaaf8c0f26349680c870a3ee09e7ee41932d342b14de2a18e01e6509b4e4c4a04879614cb84aeb83a11d66738f39520e86d597d015030980dc8dd84302100a3fdb0b64b48214a1bae1d6c00386301ed31c788650014bb1e62e0b1dc36312ea9a9a2002161ededbc31c3a6f4f0a11410f39ddc8e1016825b014176767c78b054a4078266c20d4f445a70a8f3c2fbe44a057e079a320462a5f29294310f1610159404949a0321264deb254006151240210a84558c21ec63f21707c3928539d033c51e6535d0943e10e824c2e0f1f27d016b95c252044ba9468c5a117ac4e25046d671dd03f921c1d8d2c692ef21a04a4070f982e5aee44a99e6883a10012977820d1833b39aa1c4283c220f62a1980b5a531a828830988c896b624543c772ebaf04cfaf1458a5c5f0b25cf046366340051950653211647339257d4509a5b664832b8ca84c505a16a24155e53242f16f5db93aee420465f6f67490403ad0caa05ececa892d6d191d46413226b8c371d27048cfdb9121454210a16970f1f3f6029513cc846402e49a4ac963df3e0cd9ee91d2f061601e77aef724c2ca2a1bd4141c9aae13771fa6707b99b3ccb9bebe81d5e5383e1668e7b2ce0344a6a6a8000013813ffe2192af41727e0abb07f6f39bd77229e95bef70d78968dd99baff7bed7df1b0af7f0d91c4cdce2df0d6f9bfff5e773d79fcfe139dd1742c422beba2de6c3ff138bec5d5e6693b16ccc1e9359db2fb02daa4914933cf595f7aaf75e973dcac56b5dd3dba6e0a6e4a698b3d5d6fe5aaa4e1d3a527ba44248fd75a93f29fab6a7b8949f5497928af2144588d09ca8393fa2e87ad485a80b510c0040f2242b98b587ce53cdfd4b6b98f3eb542b156f4f7f7f1984fece893a717ee72ec419a7e9b6f9c7a133defb3c47e8d316b9874b22e5ffefaade85bc2452b6b57affcf5bdd96b22ccbb22ccb721cc7711cc7711c178bc562b1582c16995aff5432b5fe99646afd13c9d4fae791a9f54f2353eb9f3253eb9f3153eb9f45a6d63f994bad7f2e975aff4c2eb5fe895c6afdf3b8d4faa771a9f54f79a9f5cf78a9f5cfe252eb9f4ca5d63f974aad7f2a955aff442ab5fe79546afdd3a8d4faa7acd4fa67acd4fa6751a9f54f6652eb9fcba4d63f9549ad7f26935aff3c26b5fe694c6afd534e6afd334e6afdb398d4fa2713a9f5cf2552eb9f4aa4d63f9348ad7f22915aff3422b5fe2923b5fe1923b5fe59446afd9379d4fae7f2a8f54fe551eb9fc9a3d63f9147ad7f1e8f5aff948f5aff8c8f5aff2c1eb5fec9346afd7369d4faa7d2a8f5cfa451eb9f48a3d63f8f46ad7f1a8d5aff8c8d5aff2c1ab5fec994b5feb994b5fea994b5fe9994b5fe8994b5fe7994b5fe6994b5fe29cb5aff2cca5aff64c65aff5cc65aff54c65aff4cc65aff44c65aff3cc65aff34c65aff9463ad7fc6b1d63f995aff5c6afd53a9f5cfa4d63f915aff3c6afdd3a8f54f59eb9fb1d63f8b5afffcb56df746af5ea809e8dcbba3a51c67df0f7e8ad375ffc7bd1ece74a287251e86ac0832089e3f7e13d3a03ef77387321db6f44e241271423dabf7c1fe76a0c26fa2bf7f5a003d1c5eccf3cf6dbef95737f84ec4a51ea6da1fc75a7fb56c2b2583e1f9f654d8c6611c26c24408395dc57db50d9e827b33345af06680f26628f166b0ce5febab7af5defcff5cd353fc3fe7e6679e5bddfcabb8b7e6fddcd377eeb1ffb9373ff39fe370f3d7da73edec9b7bf9cc74abcfd674a0af23cece05bfb6ffbcb7e1e0e6af692b7cb5f3dc7683e9acbdcde6df6febaff89db5b7733031cf86836f1b0436dbfcc3e91baeb5360e13e71fadfdf4ccbebfa0d9d66af5ff365b70f7746e783af5da9ccf0ab0d38d587fac0d1371389a162399729c66b566b151f8ff3c9898b5013d15aee9e9bfb17fa6dafedf6779b58d7b9cde5a6b3b1ac5b7b7bff3da9e46712ad5feb3bf3fed0647a3595bc30818f6b98783cfe033b52b5dcdc3faba3518866111300c7f2b8dbdda53bb8e46a3304cb5b3385d67f55ebb3aaf1d04087073a6a16738f5dff06b37bf76b19dc3f84d0cf0d6e1d5d9d515ebc6e0dcb3a1c130fce30791482f9da2de7bafe6b4d643ad560ffd80f49fa1b8a7eba067b5fa3722dea7365550f15e4873416deab560a7f74eacab395719d99f3d022e236b368944f84c04ec730f07c306ceecaa96acd97c2b5cbd16e0de6b1ab610608e70ce44efd08fe356ab87422bdc497be3d8f4f9e35928146ee6749d755695453f43a084fc79e1aea42241ea5352ac4879ddd47aef318cb6b646fc7ef30fd17b67d1df3615fdceeb9f86f557477d12148be3b59761aaadfd76138a2f1bdabbc6a71373bace96c16764543b027cac3fd6f6d22cee730fd796073aad56ff36ff7aaf49f4b54dfd70f355cf39dff6f4dbd49a89831906f52e6fbc4b14cac30ef52149e155fecd3f042c01f7abe9add57ac3784e4b612a5dc5a1d34ec928c4299298241ad1a67fb6a7686f7cd6df58acbfb182fec653fd2d7aea6f71527f8b87fa5bdcd3df229cfe16c3f4de0708a0a23a3bebef19dc5f32b7370292fe46a0d1df088305221478cd38f5d7ccda5f330dfd358bebaf59b2bf66c4fe9a7df5d74c417fcd02f4b7ec527fcb0cf5b7ac4fef1d81150103ee4ba08cabbf6503fa4b16892c108297ecaebf646efd253bf6de055051bd10dcbc10c6f4178296fe4290d25f0846fa0b61477f2110f61742547f1f50eaef8340fd7d40a7bf0fd4f4f781980786fd7dc0a3bf0f56f4f741dedf07d3fe3ed0ebef03382b5781032ffd75c0a4bf0e7ef4d7018dfe3a28d15f07797f1d78e8af03bcfe3a60ebaf8308fd7590d55f07047aef051228d0ea2fd601aa0360cdfe82a5f517ac417fc1bafa0ba6a0bf6003fafbf5a9bf5f99a004bcaf8cfebe22fafb46fbfbeaf5f775f6f795fbfb62f5d7ddd45fb7914b05a6a0bf31a778d16ba00a37add6eca4fb6f3d9d6df7ec1d0acbb043d9a9d3bf9fc7f17fed1d4a0ed49cf95b67ab2d57efd55fd0bee3f28fe36d5b6b7d87cf7536ae8c0c2a4eef7da63ddeff7144dcce712c4d5bb157ffb5d128f69a95fad97930114be7f89fd6341e8d46610965116009b0195c86a53f154a06afc1583a87e6df2aa10c4be76453ab9f42d7606b5a1a7c96e3d93b1a8dc244782b85cfcab0744e06a36dadb7b7d2573a7ba3e5b57d47adfe7ae773b0b7666b1b4be7b0f71e8e46a3b86d33c0d2f94d0332807f9ec546ad3efaa9701bfce94d00f7dedf330cbdeecbb4ed8da0d5fb8ed76c8b6fadb777fedce6abfb7bc19cbd77fce736dfdcbcf69685f99175e8bddf792f043a352028e8b69d6d57cfd9d976f3ef601c9cf1b9f5c3c1e1fec3f9d5f45c7f6ca073ee49d77b57d0b1fd1d73815badfead0769c0a937507487a5ab786ddb79b6ad77cde26f033a67a0afb3ceb5af966d6b0f5369b2dc67b1fe0a47a330b1fe81be8e88d370a0af8b46d132dcf5e9c144dc6bb567ffcff6f8fce9dc6b610f7f2d1a8ed7b635a7cdccda60224cb56106f9c71994e19fc66b7b4a5fedda9e7e2b5cd36430eeb55a62d90c9fe59e0c6ec25109de30aa1e55d57bb7fb7b40ab7728387d01dc8c1aebbd5ffbfb25f8fc562b7d41fbede6ff67fbedabbf5e9988af579cbe44e6e511b35abbfef31f8bc3dbfcfe0c96481c67b044e6e5c1f3ff33849af655291cd14f61dce77e9ec9ae304c7c4024c2b54d57a3b84dcded1aa6d2701919fc71ab8f838951d9988d46e1397bebc1575f47f16c3587bfb486ebfceaf128953e238bc2bf8afffbe79f0ae3dfdf7935ff56da3a45cbab7eb415c6a734b64d8d6afb323218f6585f0747d9f4d4aedfff9b6aa3f905da6e30f10bd2385a4dc3dfeff67c361e15269ec1443998188dc25b6999cfbd9dc33804acbf92c1384cc47ddd195e4646849babf76cbffa0e9fff5b6ddbe5535b0f9fffb7fb576f37fd5829ba01c4de7bdddf02997a877ae3a68afbaa9d0357811be5f6b7cad15b15e8add2f356c9a9f2f25671a9dad2b1365ce6800c6ea6c1650e60f8cc01dcc4daf09903d8eae1331cf6c0e1cff67445d51fdc2c23c3da775e7fe89dd774f3ad32feda86c3dbec399bf72fccf49ccd7b15187a158421da73366ffbfff28fbf0a888adaf46ecfd93c411ab7f17f43bdf5deadfda52ad0d17a801741fe22e0f0521d7aa9eebcd9f37b80cc0c8542ff6c4fa1f9c7a158ffabd0fce37316efbd9ff5b7c08c17a1b674e1d285cb162d59b05cb152c54a952a555e01673e0de2a0d27b9f9ad2bb112829bd774557fb8e7b0330e9bdbf01620cf64ebf01a267d1378092acf78edbf559b6f70ed5df005a6f0002d1283c5b3f4d85fdea776a6adb7a6e7d521428281d93e11328273d00a82f236b504db8b8819a0325e78db2532080370a8fc9d60a6fbdb577a825bd4329e91d2a0992def1edbf80b7cdf41e5edb81d43b3cc73d1d3e77c5be5050bd775c863a62a4f7dea0bffd49ef7191059fbef48eb5fe2af4fe50a842229d38ff10b09dd376fe29dae1e6102821413a69ea6e2749dded94a8f79ea0409400a84c5122da02ff3c0de8f6fd376cdde0eef6ba3a51f30e7787ba03b583b443870e0c3a1058d00910051cd8c409bf89f7c795714c2c1263b4dac64140ff7c7fbc77281427a04cf44b725048c16da016309be37708ac5d5fff40d0553fea9fa752bf4143863ebe3dbdbc27063cbb3aba0b16e42ac439e1dedcda3e45302b2383f0c0c15803060b3e7dfa446080800053525150bdf77ed53bd4870036d839767b4ff12b7d634b90986f5b8be31eaf9a7bbc409afaef6b16871a516bedfaafde9bbddb12f971ef367bb7dcb6e6f50e35f805254230009b8ba953efdda6d67370ffc7fda9cfe3b393fbb56d37ffda8fd6faa7de25217851968d5aff9ce702ff5cc72d91785e06a80d19acd0dbbeef6fe7506c16baa4e67bbd4369c8608909dcb4649deb6c1cc7301b1b9ba5b368c42c4222126e1cae49e09bc0e6a62a0098302f02ba342685454e709f3e966acedce36b905589dc780247911e46e804dd0dd8a0e5e5e6a6c32528374450ec8e20baad72ef20126be1835f954bdffdc5858caf100c870b2b4e8a49720e62be0220b367cca8d5a459a647490be1dc9f7208e44a9199d7d1e182aa329a2834b58c291cacf212153647e1886808071f9864f8f99d21b22164abe9c34c231252a2408fac68d4a0514ec24a7a15048cfcc001d7dd10be140d086731293db44d29b0123854d911690e1d1345b6e200c6480ba2614d1659e098257c7e7459e9896044c688abc23f2cd474167372586264008110f45d7e744212e207364b8101979fa1478776c441730a1a580fce18edaef2c23214b2c5488447b3699fc91a21aadc4840a1f374270f6ff1cfdad04ca0d125d569d0159b482b5624a9a12409fc2957713cd969098c0131078383be0fd7b007554afe811b57aa973d4b30bc54221562d475c40e203c2906d29825b1537187c88e21d69d9f12b8180973bc68b9a570313f7c2e1515b9cca76982343495e2d4a0a3f27a5538b1684e8f2123b8d4ba18aa726597028b0e9cbd5c706bbe2aea5041033049d2cd86f073e9d02ac5db063799e7c3221b5e90be76f4295931844e1996b119b313145919083dd93ea74a246871066c6d118328375016066a72393f09939ee2492f11d4d70f3053b79e0923452c651175d25342670d2acae100f8c60e11261b2ce6b72d7aafdd0c7c106a20090be892215982e00004b320825100b11323581f54f118da16305af59a17392ab9384109ca910d29fad2787ac50c28f276a90e90b23e2c83a20c89c4609e6e2044243d6df011f3821612c853162d4f65b2b866f05932e853910517bafe072bf9f84710cfa046587cd45d51c302331496a54b271b1afeaef6ca60393a7bb443c2d68db4b8ed6aafc99fe39fbe3e07709cac983820d3fdb87a54cc187a0dc1e4ac11c36d20c71c895aa0a843002c50da1540aeb20758669ce41101d2470009ba4997ce5cc18ce082b8703b943537a40db60c54aaaa6b4fce08669cb0c0a6217b46954e8acbcc1713871d48f07870c6081d166e38e72ac88e37d7ac36866252580a3829136949d5086788b157ed4515da4391bdea18165d52025804a4c8a120059f35510cda8e06b0bc9a667f4100ef3884061c131dcb8a540dcaf1e8cb880835a8b010f1c08270819dd7d48211017840172ab8f0c4c98009e41c5610443c369d28615baf3dd618e966455c0b4f409cfc7c3888d407a5f632eed579390933a2dd348e743918348726ec11a1ac0554cac02873004b14155806c8311423917b850652021cfb4c0c211e88ecc1a0d6e54b2fcbcb0d428706380f75b8213c118099ac264fd6b8dd31624e156aefec81d241bf0bf89a40700639a4ace078e5a0e0f4c0a21a1066daa2cc638a61c8a20748210bada3220a6934a1e9835665091ac14187e8bc845d7e2cd5cc1994c4810b88432c2bb260573c98a14185c2a338e3f0070124098fb4cc90481e584376c1794168eb80833531aca2da7cb067331179eabb381b435d200cc49f4e250438818e994202cf168a39265b7b2562f9cf4b89371a3ebd8250765858697b5422e34bf311e2ca4d824328403a620ec8b05012898eecca232b0b7a8048c2db6204b55c31a34017b30c0aecab413208ac201dd19448061c2e084495c14ff10756cc102ca741c0ad278e9b40348857ccecb85470d511fcab71a564cd448830975cb05dd151dc0ef9a8aa5478ea1b3e452894abf462540be1eee0427a7439370c0ed1e882012178d38cb485d303ea9e404a3c16ae106de1c123dd80b66385189689334309555b02191ada4581f1478e21276e22bd58d345c64f108a85297f623b3538115d8009b0fc566c0203c0860b1b907492d211e54c1e576c88d4139c9c9a5c8a4e41b462a402797d36ca4e4d046814eaa445c5f41060c46244e08c4d0e3c7d7e0a3474c981a62eb92eeaf411d0be86cf1b4a941a5c426c7c250893b4646c11143c2d831648d490a2c80a5165432330173094682a4ae8716341ba9568486960a18e1131b4316950c808317d7519612800dc0a39f1a0375c5808ae88c9dac4631108214a3d6450ac417112c2c37fa5801e7999a24904d4d1d3988b0d54c2c0407c2a043d51230272c18ec23d66573a69b97093a50a1c2a0684889e5811e4c54ca7b7ce9d4c350ea825c21bbde8706547720e130bb38144a0c71b41619243af018ce616d7c78a3521aa110986a337401476365955505155494d23fc010030827c8853b5262c4f1d3b3ddc8a51883d2738d81442c4640b4a36cf5b8d370664dcbd37025ac4ba5881c114284985c4e2d7e6465da17439c47668a07e09b407e900103b10723d045b7039af9e452bda49b694108f0ab79b885c29df724a65d27090218383833aa2f450d519125359758894234706c1f064b80be30ac497c18d42274374889c718a3186048f2b2b817140fc62154c4af415ed37f25b5503c79f941564928e64c30762f07070eb2af1741a3202278c1c06ab10f2a40842b06426ca4589927c014bdcc658a2f1c24295a53c282458051e1db1ea8f0f078a10f06210f6c950af7325c5cfcd8a2f100a58570cf950d19b78402e32a347c90deb8deb4185a09cde62c4192f71b465504f0c781dd6c164e57880d355d305497c907fb459cdb7b31a1dee4c63a0dde18075868a26f7100966c288c03b2331dc415a3934a25ff80b629b4d94aa0879722747cc152da8ac32761cd89cca58b2000947402d33847a91802a34f088f200808c0e06b62ce0f30363d0d7249324440a294e939331c8be035a7686c41610a8784d22426460f40048ed4dc21783c8b35216872a31ec6a90a02b8201d0e76ca3a551a41b5a9c164a14b788b4fc00fa2b008d34260b0551e493864d62565a862035ad40e81e0c3d42f449441a5d988c235b1e1885f89176e702559b4c17a06c61516c0b7dd92048cc1f23d8236805faac662089783ba4747d32643706058c2b2031b9bd2c447d246cf8a80bf929df067de212e02490cbdb6609944978cf1b41b0f4b80ab08716e573472c0c073ceb9685094ad514dc58dde013ba1e736566409c372471155e050cf2efd5033ed9aa4c3686135726fa9658baf23e8c069d41804346dbb18a6a88e6218a1942901205c1d364ad63ea5a0b24a7a39095c8210a014c62e2818c5d79aa13f0078fef1086373a6838ea1a94a2aa0d96ce053c704b5b08b8bb4f0e4bb20b50d7a2f7b9614c154b132fc612177c5825e4bcb4f111f10626dc841b69747d5db950079cb9343c7514d694dd358091f55271644088095d8c287012302141e4ed519654874459e035b518790e70f164288e24378885bc3387784c8f58a9e568c3888acc17da1b17090662d019b202ad0ba03f8271b26004c99126e1480243cabe432b5bd2092f81116f1938a1b8f1345909b004c5eb7fed9106e0802624c515325b0e308872434d0bcb4b393846220a13549440a2184c8aaf564e53112c0692e9cda12b8b3bf003b0184b2c711dde412b0d1e191482cf071e2d238dce5c154e287189cd1716088122acde446068275c6ea8500259989e690efb84b5a52900ac0105db834a520c2d48721c5b47074932dd921e0432b0e1ce590ac2ac5587458cd0c8c6303a544b71c124114408dbe406cab51d0d8f14ba70a7a3811cdcd6e19e348547b30659f0d426384151a8b4aa336869a2cccb1d1a2a4e9fbc00b10215e34257ea6e68e86a11c49f495398272ebf80b023411c89a1238c9600a2867a5cd55dc18d1e1e2952d3b649e311855955b195592a432586a3359aec39881a6d29e0eaa4009290448006f529439782ca9012dd151bfc8196a31289a600c156ef442a73a4aa88a52d5663d4bc31831a1c058e0192a52bc8170d5b9a6bcf3738cc60d1e89a1a605928564893a3131f3c4daa7f0e180340e1127840844e559bacb805301523353601f1a2b0e626859d0b660dea8da229ae1b37b0dc785a43e64950ec04e4002ce72aebe662dbcc11acd866f090a2d645a1ca031c2f444fea0486b1197460e8ec929b439c46643d21f5896e15e1c4080ccc8db5153bd8b87c2865598943d4c945211e4c62e51300be7084c1d812024f6bbb8ac14f4c327491d567abd654a983833792fe25281a91aeef1c140739cc0c323a0041c519380728c100738b62a4051122dc9f24459a84e29e5aad6a0c2c4c3a07528630d41d6299c3e7c6dba580073558b0205a72b6668404736e6097b2d23c56a4393270e954f667849b9456260f89a074b0f363a5a69397adf918c70a4dcb51df88038514d489a55b4566f4844591116592372ce4c29048488d3ed61a5b318e8821b36b60a542de454bc95ca4dbca88a2cf25cf570dab076fb86200251a627f0a0a9469507321900efa8a00d596425c0cee6b9728cc0226b42dde9847d4805d79d1084394a4a6f9e6cc8a54936559a82aeb409b0c64ac69065e448a202042340bd717404fc820b785fc34310177e7c99b48573cb8f88e666a165978d4956f830310aa0205b966f6ac5eec313799f8dca989757180ca12604f100d8922152addd88aca58583f324dc149953d4e08f0d006858b4cd51f2c1c49215e5028c7005a7e60a21cbb3841c21069d3874f2258d431f4a48d80b17838e00321ad84bc6699c10e34898d09d7a965d00a313d2c5dd3407950578300d5cb881839007ce2c0606e4dd09d4124703c595e6e96b83eb0b00c98af3c49f4cbb6ae943b8d125cb91e88a430f385cba21639bee86258cd71c122ed41878e9a1a7e9274c93a62246b27870d2f28d9ac15d7d272f87e9dc0e4e76dc95c9f0d33306480d230bf02a065e8601e6022c34bab23817ca78c16b1cb1d3f6ad458dc19843a0951c6597d631c3e2944644ac4edc4b507018e10476086795cb3079159510e4b7b5423d688d0025336e750900bac85130f7e8ef0b0237d3b7949c0dc54c1029d4754487c6d01b18449cf10aa91e976318c48b4460d1918485637dd82ea09a334dac208834312d81703cb26c106e9a222d8c12d36bab2e9ce56a10f6094a6606d389c5bb2810f53da1f2b45850a95a1042757d86c24e7bc21a38a589f22f5d4966b5bff10b787c022c12c5c23ea240989f6f9194910b9aaf3e8f0c6d5ccb6a150ddbbc59227434ede1c5cdbaaf0e241786b4c7060fa14b6200e86b163179722105c9c283412e36947101289988eae04b57071b5e60d0ca3fc66b569b469acea8ad5eaa766234056898fd38e74b8a8ab4b852b1d238a0812e8dd2060a58b4f1596a703e8a7d3180321fcb415d6909342c1f54a8086c4eca348252a507513ae1abad7071204d8346bd02d4e0089d2661059c73214171639bcbbd08193e5c04e04775705042b570a391c3241f24785061626fa9780248df48c6910ec29320bb084a4f7ac3f2d91de3a0c0921e20a5e001b78bdbb90d2688833e2c20c4e8414a6720abd7f55b97369eb3090109a151f855d2c059e095418c512a6ac8295c3defbeb21006517d7214c6df4981d0a7bef60d33249663a2011f3848b25af85a245a52016c0a80c36995d8153c72cab08953014521784615a5bdefa68af594ace1e135bafa6385b7850161a1fd4addcfcada60e458416351285c901b228cfa054609e4314fa9612420ce2d604894259d1bbd5162c5802d45956c24cf52e3603920b0253c69e6b0a7be762d3c02c94a1888f1d13fd2a0e14c0213c0b069df45e0501519ad87ba2e8a2772e3d210a795201858842bf8252498b2c75e4d27b550cb333b228c21cbd7705be6df508b902f78e8516a54690854b267a55a43802ea00b8d1bb5757a13b8320e1f58e25052c108b0c0addcd50286a63ad772f32573fbbc3deb38e7d2bf214f6ee9ad92a0399e85f7f961e1c7bcffa72268cb0f7b718c410850ed695c348ef5a7280897a7f0ba170077bf37bd7c2a3a277ab574eef5b207ab772d1bb1885ce75d57befbdf7de7beffd7d9225c38cd5504c810c9a2e1c55f428a2b5d727ad52053c8fda48510e814a12860d58843c099234bc594402488015281410075f09570100f3c0556ac084f01073231264add123c00e0d3240b2a22fbc3a207d307c0ddec9bda06225ebd391e9eca3ea7cf70d95197c0a143c82a6090bfb52e48c58561b58a42737ea28c2a42589923f3648085e73bef58ed645a16c921a5502be9409e14a01cd020c60a1b1a6c516c41452530b2c2f3cd606cc904e12a1a8efcc8a376d222fb3115473582f52ba0841c1a224c7dea0aa0068408072285b87854367c3c517962858cc988a8fe85bf8060a1096450d087175b52001f6698bd65c77eb3b321bf2015495230a1d317512cc282c034c13d6a8c0df15973d2d6c35746202463b5a254b0ea4e14a84a1ec029734602c5058a383100fce96aa58e61247ef2b69571dd149503e61bc5a2b517d2b6bf036284ac8601e310b4c70d10172cba364bdda9302ae4f82478a0089156abd892255e872c7b2ac5e2748faacce24f89bb3e30c6b21cb8c368c9c28d1b285e51580669316191ee698ed900ebc396c109f0bad60123b07b346a3b0896484551d209605c3259fc9a5b8edd89812385b709d1f42fae64cb032e6d1baa2c941115646e0041c143434f2743860725b3823af9e24786c1a01d956e826e8d45d6073488b3001924e4bcaf4f518534440a021608880e5afdd9a5bc51189486be47aa4b90a78ab31b0872228b063f38d78031d9c1ad38a8a522786a5ce9a18676014b4700880f665464320bf23725e1c611a233414a7097403d83aa390064136ef02b8a7c684af0359814394789808e9cc517465c398805082af2c34681c20b303584c3560294107c81c97860420ca367acc8aa72dd72654648a25890d8a00ae996ba04e43b0daf479f1c5ab5e63c082ef38a7612d0010b63a8611dcb143481c960241c193a6c1890170178060d562653251caa0a74e8ba19fc3fa70a26bcd122b5a8a690a8a38e0b105b2210b83aa3004e43c4980d167b4903084daa202c095a318537e6048ce200d7de883faf0c74217992334260c21b0f4f6014b23325974f8a9d2900308b60619170b5eb1154d2f98932b5b5319ba2a93100529e209c6c497048b2b4626191020294552b019d1047966b741ecd1a5093cf0007951080c98150aad8f64654903a23087c8248997e4b3188690e4d6a4293e4a6188145dac84d1b2e6082e85585a07c12eb919859a44a37c04551d48ac46f8708377cd1045511feb2732efa1c596873e617611650f923c176c93b6571285e514eed69cecb15157056127658b01032b024c6216300e7fc8f16e21d06c2c3e2d3674d612a1099251b1c5812090cd9e27606058bc912ef58430c403939c3b9d6cf0bd3b720a1ea62d10a5910860108b0f8277b4d45cbcfc0b1d68d3072e40205ce28a0176000b890d462a34349355284994883c7a1f3862c1c7286b39238582522edf2c654d6192c8c6a74f1f8be089b61502f4267d75a8c3204b894f1318e85960e350854544b78b94a52a0a3eb5c8aab9b229add1240f59642a2885e1f844e7318d3037252256f2aaa224edd0f2d31b1302060e050735a9b8774bfc40224404c65c000160e8decc52b08ed800e0af4f0b2b9f38396b6107b3c78b0ad0429a3a4de45ead3d2d0ebb230c5588d63cfa0237f7e6805da21c7e7a8e3cac055f40fced75a060c7c048972b0a5cfcba40445821c0cc0a9e7f72a1ea030f11992a141c8c0df64c79d70e285800a543a8e7ca88a44f8de674c87253474791163548055c6da2944814c0ca57095f0189032e51cf2ab13a0592a46a488a13c44d1a1e388ae45710e03c4770f8a3b7149f32ad05be98c3554c544da48350919df9d325ba3b864eb1110c5230e862696c8ec4044a81a8faf8aca2206d58baa22709e511a24cd60159766cf928730967a040850610452eb528804442d818e315058b707c1b8d859ccc0d020b564b18efc5a420629f8f9d0b1f255cd86d09209b0ded3708139e0cccc40002210d1b5065049e185692143c7082c5c60317c07b260e0d25591a01360934a3f20fc5f8d20294baf1e62539f2ee203d8889a21102cb53615768c49c6db54d192c694b6687574fa66207c7082341253450594bd1604f96a2091e8ca0b8a0440fa022980c428de47c1da9813cc0e00f21211fa7b1485f89a6dc43cc130f2cb5e9da90f4c023eb75a5b2a06a0461adac058a64a612273e3b74bc7961e2a2c74d20041ace2a4d527b079b395c413b64301581be2f5c5a3fc60c2442b0f4bc90c91418b83e11f3c0f6280482335e778c485479df14c04274a4e1040186a3eb524a4bbb30c601942926f498c93dc960f1d9016c0a9b92c6f5554441bec72f4497e1014d5c0e7568029aaec97bba5075554f5a0c5ea56942d26688073b56160ae55cdda1397049c884c0d30a4b735b6073535d54525400c1b8e492807003f93006c4595501c55d842c7ad83ccab3a448d71d4154571aa4832e21215c734263ce0b2fc3871d97ed05c895ba2b7558f20a465c20893122d6c6098a227e417443090854267f52605801b26003499f2b7d9635020bcbdeeab26ce1014b26b01a64156785efd09863c1981855a114367aece9b1153d13a4b02dbb5a5513d0697e724144819a979bea052e85b6dc5e166bc4804853e7000331283e30f110f6e4413698e60c6cf186912dbdf4cc5f4a43b6754262fce905b2e240881e4a2fb4a6b0d02a32f528ce6c0b10a40078d83fb0204b84bc882bc3738588d6a2344864b0784f242a936db3e7811c42f5a42f0ff0d8d16220a564c52667ca8a2f0b52fe8a330ebf166d929227818a3e4aa61e3838395169beaf9f3a4405ac0a20f2d4a5e100984768b278e428126dd3e7c6772a8464070504e0d7da76cd164f686204471488c020031a2b578935cc6a481f1e86341c74b1a41571965535c0410f1638c0f0a022a5ac0a001a3079e520f0c81393a561a7215d95c4604cc9ba6a2b7440cb1a58c350c8c983234b9439ac184a02c842c95ae141831509b620d1c0a7eb112197794a850c900ca94fea1becb30a802efccfa3331e3ada88a1648202d58865c057dd69a2058c08362991a27825c674d169daa3d27360b8270187468133912955582f00205ce12183809c26bc0d1b2138573218b00951048c6d8083524e980441ca7049893022818133366e260538e3e8c6dba42a042288646938781aa3f506e44a0408ed55951e4d55304df28405bb3aa1e787138b0bab2a500c0ba43054631a8ce0f53b1bc436e8ed8070e40f4a0d34ac814250072459f5c8fde449d3806e3b481500c7c412ed16891b5876980cc578f029e102053c472f6c48f287112c3c208ca0030561a4498026560c06c149a890088c72448491244d8ec0d062f483491cb03f597be836c4b8bba2b2821d3a911765682f0821a01044067a14b478a754d5111467efc49e184abe00a0a60916ad0e5804f6bc990dc24024b41e60e6600066ecba5055c7d20f4382c716090668327b2744aa17a9d89a88638ddaa99ed9d4252b4e2318491cf0102455615b00c5a56693af1488a0d8d59df4c071f55d2064654d9e4b07eb0643800f4bd8d4b853a3c6f36027ab02d5a71f97050318a31be35ca1ec9c571e3913d87a4c0f9d991043e1cbc30d20ae1f0a082dc4d86c3e72b42edc2e228a609109ff33fd745955c638d49e8f31f2cef1e4d6e98f726c44fa78e98f7391a9642aed3640a981ceff67a312c9fcd325f3583c4a9155653cd3d08fdde6caba8d48dcd63e69b4db00756a9df251294f8f49a68c9c1a8b54a40ce844328b4bd928cb4869b1e8e54fad2a63643c9775fe2f27ff542935fa647236c6d3aa322ec6cce4d6ef2fdd72e7f4fb63f22f95f29799d3b9e8a176be54c39d4a592a2b99cba3971e8fcc625269e7c8db995c7aa34c5d2e9149ef652475aa8c95d76c1dc2692c3293bf389da9466afc8f5e5955c6c9b9fed78f8ce5586901eb944e6325f538954ea553e33179f45565bca4e659269edf1fb0b6a7cb3bc78db673f6decb4aa32f1a95cbe4d138d7ce2933c94cca46668c9cfa69923aad2a63648ed3744ce6d5799cff9f97546f5c2e7f8ca4fe6fc566f58a56e757528f54cf4c4ea746aad427e7aa3246e6c5f132bf52ff9cc9e93f52934b6a91197f794a45d2209ccc6ffcff785422a772b138cfcb65d26e03d48fe6ab3a8b47efa5c7e592294f997e6a5c5291513fe771492d4e934979f64ca44c9da5b41f3d43add96b3156a33aa9d4a37c347ea3b2184ba5d278ae2a63596b63a1d6ec75593c06ca463fda6d99bc666be6771691f2927a44268fcce2f49844ce55659ce33fbfed2a2e1be5662773395526997291295d1667e4ff55659c4f7de087d6b6d55fe9e967cac8a973eaff3c237f721a33636a71e99912a44ee3918a54523dd268342e97c82f23bd538a3c1699d358191ba9cae5148f3ae5652c9d934526753e32934a39ae2a63e419d4b9948bc72253a9fc52247589948b734d6bebac5d439771f38393993c4e9745ff8fd2658cf453a95f4dff318b1e9cb354494d2693f33229238f49663c7bdbbaf578b61d9178db7353baf67844e2f2ceb1821d9c7f1a4be5af9cc6cc9979a4cecb63323b16f71c5bca6c1c9c48a49169f433359e65a46c544a8f731b9cb18c9491d269f2e88dcc2973fae50a1a9cd22513f98b3e9ec6b23419cfb3de58dc734cae90c1599467a6515aa42aa5543f1b8fc63ea71139278fd3695c943f3259442af99c53e65499542665a5723e16639fccf1a273cf3953e329f217e37969942ae7a9d26adb52393d27d5c8341e8bb194899cffd4f88ff2b762b339d299e7f433327994a55ef6463f1795735519438dd064dcd3499d8b5365729ee564114995e3692cb761701a915226323632a5465f947eb95855c6787e7b2c74fe3974f658663c01cf297ff9539354e592aa544a95c9655519cf1e6b24de398f73b22833957392a98c8bd2a4b4aa8ce5383b271529538dcad9284b3db5381f9555656cf5b7dfecb1727cff9cb9ac73ca48cf9c97f2722a2795b271665695b1f6019df3cf1fc954c6f271aaa41ea754631bd26e0334c305a7329652a7465f642e7f91a994c655659c077ea85f56fa6768d257651957debe3a3de65940ca7231693c168b4566b17894e31879f4caa3722935c63f599c915466f1e7583fb7cd1e8f48c491778eb5e79c46ead4cf4bd92f97466972995442e59cc56252464ea55fb98ce3657229c56f8fcdafd463052752ea8bd3631c2fbd9fce4aaab2aa8c97637a15c696729cb5a9779e77b5ef38229148bc73dc8abdd271f29aadab714ea4723ec6c77856267d2ccb466a55191fe36bb62e3a9dc6e34c8d8b54e352a994a931f3b6a5be015c3149f554e9b4b8944efd527a94c6b185b7cf9cfa29d31f91dec8341a7d55191b633a3765511a23fff13391c6d8384fbf1c5f687316a7f231999ca9f3d14f8df1cc9c679b192f2938a7546552ba344e95c522f2cf4a635519cf767382d37b24759e63a5f2c754a4a726e7d96673c646e9d128954aa952a3f168fccb2273cdc93c32954919e9a5733129a52ea955656cc55e69a8afe24ba59a9329454a63a5b1382b99d3a9916a946509129cb18fe523d33397b251f64523b38a2b934d2732a9f4532f4dc6f1f16834fea27db56f6c2c379945249219cbc96311191b8fcbe551b954c6c9788e974caaf77ea6fa24d6bee7d8c2d2392799d26f9c9346651189a432a9553b37ca4a67919994fae25c4c2aff7c9c2ee3aa32864ee9dce7b81c23ed3640e3a4f353632913f9ff521a2be7696c6426ed3640a548a7514e1e995f598ce5e972294d3289442b916844f61d9dccd817fd3ffe6471feff3895abcad8ce6b1a1ac7c92512971a9d48e9527a944e635ff472514a3d5695f1f2dff6dbedf9a4729cb4db0055ca57db865ae71b0a2d103b67647179a4ce54a62f4a6559294dce159d32b328fd1fc99c99c864d2c864569531156a8ce338194f3fa0f12ecd89645291df3827bf577a6a7159ac2ae32a9ec5426f9b9a8ce988c9e51219cbcca21c17934924535e329147a6ac949346652cc748d91ba55565acb56f3f687c01cd2923a771f2c85c329154a62c27e5aa329e6b0f85da732c2bc7f42a8cb58dbd8de98de9d18d4d3f9fd63f991ace9c32f523a93e191f8df137ced35f55c650653cfd8050adbf95cb084e999a9cfe2ffbe9742a5397d26355192f63a519b4cc03191d04675c642ae3e9ec8f4ce391298d93c8fc4a2d3e888fc82ffdd3a474ca2c4a99d4aa32be7d755a8c97810e64e631a934529354d928957d5519df390e65fe9cbefb9648bb0d50c031a78c4c26a5729169344a8f4af92babcaf8fe754d43e538995fa946a4dd0668b2c1d2489d2a3dd588542a95c525135955c6f9956af4cb4a3f1ceec78a72166a44da6d80fe3198a74b3959f453e99129277d3149241289f2f12f9099c918292365a652995c1a99c86251265e01ad56a2f3fb2553898c63a554964a7dacac2ae331bd0a6344627ea512c7f4e8c6a0350dc5daf7ac34de39d6f86f7b7ab4db0085e2d91b0aed12631046cdb36060f1af42a1539cbe7e7d41f0c2b9babab8ccb6fcbfbbb4c2b2e2efc785157f5dc564563294a67e066f7cfb6f9d7ab931f4ab4afed02452fef49d9b299013dc0e10c8577b0abd3fa03dfd37141aff25913276fa7125d5964adf1fb0c10128d48181407f152b10436d2bd4b67211b8860db8eddc7f093826c10204dada3c6cca57f130a9a82452868aa14b3986caffcf5ba550bf9aaee2be0acd033fdcc30cbd547481bae25e4bf4faeaffeaeb29af15340ef43663f2b4750454070c14203040408029a9a8fec953274e9b02d09409d3254b95284d92140952efddacea18042daaf0406f13664b5bef5d2a8dd87befef9feadcdf3fd2fefe89f6dee3a4d0a4cc7a6f8316e104b5f739b06817a5c5ba0a6baf9f391dbf89bd4b9d49459032ebbd4bbb0c08c5e27e8c7e1cf8b92a839bf019dc24fb397d6b67ffef6332db6744efd9e9e7c3696c1b4e63bd5f6dc55e692caea7e5e3dbf4e8e8af1ebdfeeab18a13eb0fba5667be7938f5374f96fee611d1df3c1dfa9b47ee6f1eaafee271d45f3c77fa8b474a7ff100f6178fd3eaa7365d3d9797d3b6dd3d7bb7daa667eff6f6c4abf94dbb516dfbc69bbddbb7fa3c2fa7e79aa65adffc689c38ffdce312e86d0d4bbdf56ae778765acd6f5a0a7bb89ebd142ed3da56fc7fead663e7aab897c343a177a9b1dea51af42ec5406a01dc7bef5d0aac77a9afdea5bca4bafcfdfded442291482412793c1e8fc7e3f1783c1a8d46a3d168341a8db22ccbb22ccbb21cc7711cc7711cc745b92817e5a25c948b72512eca45b92833994c2693c9643299cbe572b95c2e97cba552a9542a954aa552994c2693c96432994c229148241289442291c7e3f1783c1e8fc7a3d168341a8d46a3d128cbb22ccbb22ccb711cc7711cc7c562b1582c168bc562cc6432994c2693c9642e97cbe572b95c2e974aa552a9542a954a6532994c2693c964328944229148241289441e8fc7e3f1783c1e8f46a3d168341a8d46a32ccbb22ccbb22cc7711cc7711cc7c598b954269147a31cd75de7ba863729bad5ffbc7fe36dadf5dd6cd7797c9e3af39c1e9c1be6f2a0704eb8bb373b27f479f77c3aa14eba3db8bb3a6886b63a8fcfd34f07f8fd6adbcae6e7f5f0eaff67eb2dd9ae3f9f539b7e3edccfb6b22db373bfa6fd6ae79a1c7d4d552f2383ea518f7aff77ef518e981c0e4aa138eeb1534f6dc428af7d4d5fe97b8ad3337ec5bd16b6e5702cee730f8743adb62dddfa3f9bed4ccde77ee6bb80f575ffdf7a6e6ad6de345f7a1a6c271289bd47152234a8f7a8405180a2fe44f941e381b6f97af7703cf3854fd49e283d5179a2f044dd31e3a6f73ef72f349bff968d3d2acfcc9f993263bd8c482492c1ce6799b163328be7b615cf6d6b1e1915fd674378ec1d16eb97f31f73028ac5fa69b3c98c9148f9c6c544b9ebbda3e86f98409d962ee5188a87f9db9b7f201d076d36e1dba68699ea502c6ee7eda8358d46ade937bd47b9896af38239c36fa24d7f2a20c5042a8a02141eb6dea1e640bd813a843ad2e5accffedfbd43cfded46c9d4bb33d4ac181a8a84d5098de2d73b6de42b745adf71eddf2d5b578d212a9f78edde65adc6829d345cb95de7b19dcacaf59b87946a6c58616b8be66adf614867f4d5bb3d37fe3fe7ab5a7771ef8ab7ef67d67f15fdbd6326a4da3a545806f1bb6f376706efb91c11fc67f6e6743c0fe4bffcae2e26231db01ad582c16b31dc8a20c4678388b36a3f0f48390da70ed6f8f8dbb73024151567f2b54fb6bb2eded6f5f9d42719a48b5fe0402baaec0591913c5266a4dd461ef516a7a8f4a1385a68420379456303a2c2c6128424ccd303796003c34c4a601615909b726861a859e2e25109879159aaa4148028f1ea12f39f71e12469cdc59a0c0611089ab9f0b4e3a0e2912ca426a2e7a360809a9af4e4c1f98a68c8093e3b043990cba113db402a62c583330627ae36f4c170f0923d2f00ca98492320d6a42c0b705afa466f08c812769cc982d070909bcf88ba317c0a086256e715b2d2e0d8ead80334450005444a4bf0354362de9447481039422c28c38112ba40e45bd58a323022f0e236493071f115d879c0b3069f8206868c594d7598512825494c9c106d62f2822068ce8128a2d1857408209b43e19826e21272a0c43ecb6cf518d01ad60dd5609211109370ed7b63702ba342685f1566d905589dc88623bc0cbcd4d874b5081740691580b1ffc0285e07061c5493149a05f9366991ea52d81b2a3c305556534693dcd5138221ad2417bd8f461a61189295a1cd2ab2060e4075aa8941edaa614582d591cc0186941360052d213c1888c11588062c8000221e8bd00bef033f4e8d02e0d502f2f2c43219b0c40d88d04143a4f77b28ba43a0dba6223297b258e273b2d813159134afe811b57bfac85237600e1493990c5baf3530217a3960ba2a22297f934cd9f5438b1684e8f2179960e9cbd5c702b6fcb86f073e9f02a3f00e96b479f1216f89d0c849e6c9f537891405918a8c9e5e0d61f60a66ebd13787250510e07c0377894dd0c7c107ae07e634104a300e227378f7acd8b1c955edc5231038abc5db273a379ba81109194170293a732595c33fa8430231fff08e221148246962e9d6c68f80b91e1465adc76c517a201130764ba1f170b09b7811c7328c272297b8065c6891e5605cc082e888b372c1e96cbcc1713871d4ad88582ec7873cd72634f19e10c31f6aed98252025804a4c8613bbd9a667f4100edaa41391e7d197156f3995a3022000fa84e1204118f4d274ad4212c3c0171f2f3514b70126644bb6bd40216502903a30cd5750ef61a442fcbcb0d4287aed4264fd6b8dd31baca0bf89a40f006d64e510d08336d515ad5a4b1de20ecf263a9668ed5c3150f666850a1b04230432279600df94729aacd077b36fb0bf3e762af442cff79f9cbd3f6a844c657fc03918811d472c50c0320ac403aa22991100051c2102ca741000e620ec4151dc5ed908f8a0ea417a35a08778776020342f0a62989ce116de1c123dd4053d812c8d0d04e0a6d808c9f20140b54aa00f8add80406800dd5226243a49ee0e4aa7f55242302676c72e0a942b9a8d347406be5e7264198a425638cfc7684a8b2a111d80b3f5f8986940616ecf89d75196128001c0beaa54d3c168110b2a49691027ae4658a26ea0c109f0a414fd4a0f6b97093a50a1c2a2a833a7732d538b0f61f8985d94022d0ff2d5c1f2bd694e8a7a8a0a2aa929a46df2e4f1d3b3dfcea6f41c9e679abf1361302832950922a394bd9a181fa2510dfbc755e3d8b560267b8b9ea0c89a9ac4a64aa27105f06370a9ea648ac04c601f193a7d81a38fea4ac20336d96783a0d1989330d2033512e4a94442a470a8441d82743dd8ef45f31e443456f228dfda6c33a98ac1ca97c9ae6db598d0e77bc8d4324980923027d874afe41193b0e6c4e66a29390802a34f0888a8289b6b03324b6804025fa4cc21783c8b352140c803e671b2d8ea088fc00fa2b009504b5a2650852d30a0428091947b63c30baa1725814db425f38403f0092dbcb42d457e24313014e02b9bc6e3ee401f6d0a27cee7ca0f0a140e155c020ff7ab063a26f89a52beba1906a88e621ca190f20d602c9e92874f570ecf3d0db60e95cc0b71dd800ea5af43e3974d05142ce4b1b9fc10e5e2ed40167aed821421d874b521d126581d73890096221efcc211e1c64a0bd7191604006073e0e0c3634da230dc0016dc8062d585ecac131121b50c81bea3668196974e6aa708286411aa26808d4f04646430264863c198e64b03330ff324c41ea9323a3cf47a0bed9445f912f003e34168a15d2e4f0c467838fc3cbe7c08da735649e047b92b06de6085613f6bcecb17842ea13dd32b287810fa52c2b7189f6f40ef5d9aa3555eaa057650e8a831c6646e88dd0ab508d818549e740efed52c0831a6c589e9f4b59691e2bd3bc25777938551e5b1419512679287904c69e75c09e449ef4f3f8ec18d60862c87188217ac560c6012a4b803d117896f0c6e0c180e2ede12df071c7e82e4bd6dd893bba3bbb4176865476da353bb7d90e411d9e3a237575ddf24e4ad1044d96eaa46eba05d5dba08b03964d820de245d7836e4a977641d385335617089b5cd8d076610c8e854816ba585861018357023424e61f16e60ce173543acc0dcec5cdd1c9f5d9ca29f1723722b9b52472032ad0a930a442955005639ca7b8c3381c5a71d0b8b245ce4ace31ce17514e3da413b601b7080e0b9c09b80b706f82de9ebc01beb5bd51b9dd7133e2767553ba451542e1376f3e0f3c2aa6af9c36a20a1399409871518382060a637d42a4de7b3f26b3c938eb57fb2271c961ef1d3eb39b5409187a9918ea735c94ff7cff9c9ed2566ce0cfd61febafda5f6ffd7d7b267a1d66b2ec51f8fef9d4dfbe3a65b215f5ab694ebdf7e81245ef9240bd47a3bd4789e93d2a4cef5160bef41e65854acf235c82d795a8e8f78657c921366d9b475589a2823fe1abdfc7e16aeeb1b0d5dad66cf6dee04e729804aea0654100332f92ea1139bd7723d622753ab1b875c5805da3b7c167a0a0b88e224951656546c2cd17649a45335129431d5e758fd8103429c0a809e0420e4ede181156f65ce2ba3027195ba7ae030e001a779489c874a5b6e4c38c485230ac052274085c90164055718521360d41ea2f76184c8976c938e442a456a45307b41101389aaaa3120aa270b5a593dd3c07c693b42f89e489e044afc2d1290f5ed3d3f70acc9422c7f383dac5424ac98a0d1e0422541a43474f86d91ad6f2aeac431e5887cf835f022705bccc559af2382244186de2167ade0b8a48b3789571c85a22824a9042c0223c649ca93eac2e52e00278fab1c5597ac13f4c481aba5207720edb1ac913a23a2c15faf42192971cb51314e8ada24f1d12243b6dda74655128fa51bba879f47f6890e144cd8808d70da5158c0e89485a2448d09a5e27bb355f22c4d40c73e3d71329a84d5e8885f2ac43436c1a109211fa12e5c749871830b3b789a146a1a7cbdd1a202ca64e3e59700166e65568aa0661092699f004f8f3e11068e911fa9273ebf9d4256a38456551103caf933b0b143808f2e10f459222e362c416cccf05271d47540da9035c448220d04143ab909a8b9e0592240801417483789ce4b1faeac4f481498a962903ba8ef078215985e538ec5026834e0317124cd631465b625b5a015316ac150824080f4d24301781b8ac6efc8de9e221a1ab6e8f9292853a8704c5f00ca9849232996595510afb2386c78c0b01df16bc521a17ae173243244a00d2bb32f0248d19b3a5750f27584b0f2add5b1581177f71f434459252ec3adb32c226b996b8c56db5b8546788cb8c60872787c05cad80334450c007245a2ac4d1910589119baabf0354362d2f64d12460829340319000e882039422c20ca7241ad8ef67c6a7430cb0a40e45bd589b43e3c08c300b783e31b2ab8ac308d9e4b1274f81352a006c7dbc38d075c8b90093860f0fb2692820915962f262ad98f23aab406285998caa983c4c5c98b8cae46003eb57eeadc13e1468f2795e3160449750ea308f0e8f645c35a2c4b20a483081d617a3cd24a699bc0a4980d0b9859ca8300ced3b1c3cadc0aa6270edeba8c68056900e468438094a5c0d08a34ea874d1e826a222640a9911000002000000058311003040281c0e86c311a964389013001400025dae64aa4c9c0aa46992a33848198408218618203020203240a815e5b618143a94b9c6fad2489090da105617efbd55c4a7bf270f29e48042f429084a5150d270660f060f52ea8cc173fde12560a12e49dbc4a035224bf752ab533ff39e802bbe93360d3eca46f2acf5bc1140fe269793944238405a2ced57556b7db3ba3cf799e84de7b1720eecb13e4fcdc5261245760dd919f9c706fc4ff6a6239360277443e3ecde134fb0d3056eb11b281bb73819cc12de469d4060174f60b13c731c36969668ecdc31866acceaba1e03b100ca2782714e3c448468d24ca42e8456f1b85e5ccf18292fc77bcf4b112be1abb326eece396d343a2a80a1f1c79bbb87150f46418e2071854ac07de27ea468a9140221b8c6953a9e6748f54cd14b8e941e7b8f4d2c2ad3e8e036603e1a6a0eb78f14f8b0e2922118ae982abe11ca2ee9f56f17fa5c2db21d49d70a1167a36af0fb385477e190f35891fce65339f480052ff30543f0d495fdb90d600c9878723b80cb1b50a8f41e9e2197ddb3b81b66733297b14060c5b64cea4a236e4ea721f0b5a272a7a14787ed7971844a76f4531417c001cee12a73280ccde82ee576f91c43c6b1a18e4bd361261ef83612990fe783e6b7b802cfa3f459845475661e94a30fae1540a996461a8da975aaac46d349b0f198a4eb76aea5e69372a210f688f5c9efb0e7f88ea6da021ceb96b64efad47ecd15ffc3880ee19ea6655b1c34e34a8da3a1b8f18f59c93d1d5a484fc2863a9533de7c4e8a2ab490bc236d45dabffe4cf887cc0bada42231f69c29bbe1be8843285637aa3b5eabce7b9d07382a7446051e79342d83960e4ac419fd5ab744818634131d46b31dc570a6d134f8f9e7a29a4875c81a6b103ab44fea558a1f13da57e05d779b4e13dd517efed52b023852f7bcd5c30eeb5bcdc36b12cbf67f2272f8e08c7cdaddc43c2afdd2667e0d2487c6cf9556280b4038c156d52f547149a0ec67ed1af816a5283a5047627fb8a6fd38506df3b9ce597c4e4b58ee141d2764fb8c9be500f860cf8a64acdd1f4387f180a159ee21b8ce42fa47714a4505fd346200a9767a3c749778f245dc7a940802352b7399421ae30707ef075b1b78653551f36c6a2f40cac7359eafe4c17e5d6847351454458fa3055dc4bd1f73bcbdcdf482cbfd8ba2ea47eb700ccc785cac6cf0c890c8825961804e3fa70543f91dd22ec5594b434982c8b618a32031681128fd143028946f150a4eab0a5ee1d9f74a193de0b4d05ef8f4be29a5cc40a8b34e0eab89d749ae7e172d7c9af25d474bec4bf9fc3474fad0fe3285392fa7681b3fc20b53671b2cf63737425e49c0b60470a69dcf8e42af9280a322656334210545d3ca7552e18c0b52d1e52ebd317b8e7de84f159ee49daf6415a9c592938dc87c33f8db4b9be9723db5891261cfa47935869d52520f7d26fbb69eb00af1cfb9294254fd56d92c9be9b7d9b288711002dce4c049f5f9611604990dcce1ae4d45597a98834cb1d5c319c5349859705b0244ee6f001fdf2712f83803364b47efaba5b37ba909d394a719cf65504a2998f864919c4a7b2750705373f08599e5a053a071318651404d76ab364bf09065166c5b9c745f38c852fe6ab37cfd5b306c0b838035093ca3027679147e46667404a26538bd9f0fca48b86c88cce60481cf8a49861715e4f0ff500f187de40e4f3ad48b46fcc0d60be4a2eb4ffdc3c50816dfbde8dc0bbee09dcb4337ee5498b55e84db95f76ac8918d6a66f3678fc807a74f1989385917e2ca4c14105a8c67aa7e922f8e2cdcb727d8b33bedf5f340701f96319b4d08c8f2ea36fbafd2d04c818adffead56c5ea881b2a817ca91736580c3a7d204a68858301658015131f8332ac77c51661a0f67c4aaf13deeed6333b7496e24e7d3a8741ea26b4ba9bb4f23cc7362a7454759adc1b4a07f3a593e61c6a7dc16d77de49671bc6270d481893b5614bc11fa9fbb1301abfbb4084a1ff80b8d2c1e92b615e6da11885b8dc0cea882e1478b2315ed30267d8425ce16302f54884b7d90ba72a98b2ec26e5b8e8a4bf4b334799019a1149b1f1d79d091dea65e2e19a8f876fa58431dd22e5d7238fbc39eb73bc743ea1c06e2a02d67f692a440c3ac6bd2080be41ef34aa8ac1e42c83ebace1f4d9a1e57d48296229f5627e24d0acc67ef7ed056efd41a62e37c9482b3819a83022dcda56d16b545b936cbf2b629a3a5e5e371d4659810a152d607d4a37f609eb1a7c9f4b9dab2a4e80711cea7d0f37a9b136a1880aa9af9251e5a2ccc2d098fde5cc5c1e36985bf6a02657cff1015521f6e064c8b81f33a0ee64115967cef8003d7e6ed37f726d12f635586e056b4713c63df026bb3eb500299e25390e33f7a9ec7298add3f500b6203a9b0b78689ce9286e46439d1bdb6c009f8150c6436a19b16f782c43f099856d7936cc145086a0f99d1979522c5687eaefac90cf8d5dfb40b11d8c310e407b409dc7469a8ec40e38af83718f863105cbce34b00323dbc3ee3209dea0a06d43764c03690a3ebbb00dcf019f02c73254c7b341cf00b10bc6dea8d863a38c07e2321fd6c9886d03989905651a5a9391fde6c31904cf5064db13234676b45db6d5721837786e33a98dfd6c4262fb73cd2d6b93aa41929ac151e2332e62db43019f0a14fbb0753c37d6734031098f6793c29e3090ed91f8991dde2143b61b8393b110190c5b8b717b9b1f9451f0d914b1ed0903cc06913dd8f91d32e479985b02c7b141b19e18b72d784c1a05df4c119b8dc8f86c90cc87adddd3274b244773a09c3c3f72c86926c624bafc7960f63924fa50148380bca2e0fe5a34e6b5c07c4f80f7e25de8c5e0f5e01af9a606f1c434793a8e8e813c8745aaa2300c659e36d621a8ed9340fdc502e04331d9f8949569d8942f4949ca4295434562be3cdd38bdc103b0ecb1363602604f6ee0630bee81ad141f57c947cb4068a030307047231575673b1bc11b0cb13011e98b3dda584bb1c70242773bf601b0e74635641569e014fba72c317c2be3793bef40c1e29324ec8fe0192fe42e204e55c27ac81a128bc64c31e3c0c9f14b6fb2e9248388d6339181764468842f54045ccb5d1f3c9df503fc6d0bc6572dad434a0f93e973e50e0188be081aa3877bd76656b9d2616faf3ff78cda4eb1833f8b593ea3acee0dcb4f0280f2457d7971b2cf24fa0ee7ad238ad631a48b22c8533e187aa97998c9e62c977803c040ceb7bdf90825bf1c46dcb967a02c6b75caf2d7006100c5c00d0e9de1cbe7e6b8ca1bf43842eeee88c50dcf55c0123f7c856eed859dfc4bfa099da834a57e35103e4cc9582f544427fe1c1ec730595d14461c4969430969bfbd779d9065a45f4763fff12fe1d983fe9bca9c0f4d4c5f0fcebb6c311ffc43b5133e4ee28760653b673ff1ddf3e9224b4ad7526c4bba3b34e7cfeac8a4a62155b3662b116e2ec0af2364577d0479eb023da51d8b63bbe7351f832e18a27813fba3208b84282369e5dbf7a51aece7e967a0799c76c88534d3e87211e0deec78fac4f0a474826bd958b42e275c3950d80f6f0a141c7fe44cf92f4760329113fde4a8969818ec8d53c9ecbbbf80698a9621097dfc0a2ec9276bc3d9b5a8dcbce53b374e45920dfc567d9acc759c099bde7aa1ea580c5a6308c12be713768e0ed5ffcfa885b005307484d8963a0a91848e44bcf960ad2984af1f1c90dd11dd776063df630b34cf8d76fefd338c023630091a92be958ea020a7f538d1d5337adf510cc8881e7c4e973f7b00e9acf51d435a2d63e1c86749baf878cdf550ed27ae510ed17699f4f6e1fccb2c451c9c30adf92f5dc0fa7bf84a951f7202cf6183613ca53ae73c8ffc27ac0475e10f86cd59e379579f11ee27a005c0b115c0c609441f1a0ecb82e0f0bfdfd37fe2bde05eebc2ff3591cbfbe799d8eef1a4955bc666d6a1fc4cee1a370e0add3deb37192bdc96d261ed28650faabdd6ea238f65400f7a4407921a54c533fdf8739fa01c8a1a659b04351f05fe6c328fe522aed4d9a1d4b14add9f31b84368e556f3ce3422a4a8b615916308134707248b18c634642051c72c6e0ed9663b5d13b6deb9744e80b581840ec781b2fade84e5250a277a964ee8f7de2eb3d13fe9f06d47baa8d0ac7dfcf737817744bbec3e5972a5a401041862dc80ee1a7c2753acc9004ddfeccd1eeaa0c809c790010e1c0ceedfc66cb2b1e57e9f64713bb21255f2f4cd0e199f8b51ace4f245f2e26bc295a33f115bf7a764fdfbe50cca5f459bf30baa56d0d7c8db6dbe485e504ff9a81b8a9f9f5f0085375203d9fa6aa137bff450bc3f5124efaf8c7eaaa3004a1e592ba4375522e58924b12ae9954ccaa793b408a53fa5945d2a79b5d2316229b55a122797ceeba51004935f313dab9229a26812c9aae9f42a9c72fcb493dd503efd29a142b386b2a1a2bec9a8fc74944d485d51520152cab496ba28a6420835a5a1a78e08aa108aca4852bdd25429449597aafa20ab52e92a81c2ea9fb28a21adc468abb7e22a9fba32c8abcbfa2a705560891459172a2bbfccb2d6597f84567c4acb5f6a5dd05ae9c5965bb5f554722b5ac165adb85e48ae109a4b55741d525d09ca2e05dd75a7f08aa1bcbca4d7aff60a487cb9aaaf97f22b457f890bb05f0a2c850433d560578bb0282acc5386ddabc3f208313d25f621c5226b319f18bb4e8d4596632e7aec4790e557644a92ec9f264b23ca1caaeceab2aca38a88c2a833a415b8c6a68dd50b8c752dc0e3048df254e04f282fcea1fc3dd2440db5ab23eb437bc0b6cea2ee7256312320bc61cf596cb3405ca89ae4c62603e482764afa4a22b0a59c60e1cda8903b4ab56970c17e48ea08f726b8a3d373b6e2fd29d2a7f7a6f25952766d538411dff8e748c21188e9dc234933f13768700bee32cab62bc13752a770854e88d92074e530f8cb9b4bb02c542afb9acb146203012b83352facf05d2729716affc2d303a79869d4607c6dda9426841edba0cef311c2b59e83883eb64aaa4b0515096e08bd37547401be50162eca3a1e8d9decb8e1b3ae8596bb741f978ffff77735e2c58d05a74732aeeea6d80d0b58f89e1f1313aa0605f23147bd06d5b3dc0de2f1fbe982fb882f3e13955d566047719be9cbd95c38029e8691b68c3703fe806a0753fae986c2e049cf4ccbdf5320ae590a4427c2678cbad88ca1a058ae499787dfc4ece71bf9ab21fb00fde43add60889e01f45337e2fbe80540cf067a33440fdc4637d4cd5e41d11df4011df8199570b16d024dcc643e8134e60752317a63face3adfb1cbf8826cc3b64747890b6616148a68e474a9d80fbba73cc034e0c026bc457f5d68f48f60ac73d7989dad7a68c8908185d64b742512fd2e98620a136fa7060e148256554b5864f23e98408373ed9fcb5176a344d70ef33afcfb18a2c02b4c37ba362ff2d4c6c37914ffbd12d1ecc58facf56e62215871fafe699c697a76d000f678326ed21efde19265795c073f80fa1fcc5b671e2d92223ce69f4ac44438e361d3d4cd6bce9e039efe51ed7b1dce7bc78f3ede2f3f37580ca6191a2e8924a2eae54f5c9681fc2abf11b0b7fa067afa7a4719a3d0e25254d589380d5ed3464670e2ad6c0b5b62e425c8f7052a4f8f51eb2751aa11ce70c2948eb5884cee36dc3d41946195d7e145448fa884867f0e2a224e14579218a3550ef9ed0bf59d830c8c1f834fa4a651a18be05231ec5783fe1089e49c7fcb17e8ce821b88709ae5cb8a98a39f6d99e795cc38d5b9746b9a2a6e69f056d0e49a222e3270499be9c637a37afd78ce75c20cd841e36847edd749a7608759c8d05daf2ee847606942067a4c25b4b0985e5e1cdae066fb1847c79c281f85fdaa06ffcdf51b3a70be9e5fae1986ec10db249ac08cc04c788ef0d26e0cb99ea7dd702f2385533ecefe5cbdf8d4e95cb5c24a8842af700f00f29bf5ec867cb9f88443be0ac6967bbd1acc23793c2bf6c13a598e2e3b9c27d8857fa22fdfde58cccbc5afdc448f7064ac1632bce1f98bb584faca48d8b9af9a16a25129fdf010f7dd5c15627d5a2930db316c9b08c7e8de3ec88d14adf643106ed2986b5791c4499a3b3aeada09321aca703cf35f1e4dfa37ad4ae0133cd539649c5add88e7d84e5982b26f1ca294e6960477d7d7052a7e049f228f1c4cf78719dfe53c57bf06189c1595049c9df798e01c40db92acf74f09f8fd7d238799f72e2099c9dd6965871059d64d7f93ee40303b3f33de7df0f38717a1a28053649be86f8c38ac0ec9d97df4d2d1c6ecb1ec2e8f2d5cb483ff342922ddf964a94049b7cd3e1e2044166661a054aa728e351f2f0e9d0192624bb46693763d3bd1d31093a180a0068f5765cb13bfd8288bd7ae02c26948e8d1e18dc2d89a05721e4cfbe03eb3fa4f397b6c961cb3cb65292abb4f9614d9cdb134637fb1143a2176ea4ba4647df172eacb8ab4bee0ddf5a5d8af2f92a5be7c90f581615706db1b7a2fbcd0ac4e3fd294d4fc372ba4125edcbe8c717dc08d17afce23a8e6cf616d98acbf628bae0da2f59c745cd95a0cc39d8a2cc178e6c4f554064b98d92326031f252fcdd808377f1f73dd317f103d441abd5d5233e71e0864e0566ace1e9cde541bc63a843f0139541b2eda1797181cc63e4deee0a0bea44ba7bababe4b9350f2f4f770834caa40b8a72658b66461fa3992e5ec445c01a00a6d3b088a3108346e8630d4180c1c3f5ee0d55325c2f7ac25c5bb157343c5a469ca1d51d49cc2637a055d1a8260c92ea1eb1ed43d359b54dd8dac9d04f5ad8747e91ccae405cc5fb6ce316bf29233464c4281525546302a9796e1a8b88abb9fe7b935e19e614fa6e35e59733d6c82981471b3f193fb488df86c5e250ee48a5c160e9d017012b7dc3977d8bcad4b14f8a31a5f7cb3e07282baa494892b4c47c17dcca254b8174e8524064b9d74741c6f944a66664a10c21b352f0942f1b68d3937943befc27ebff84e251ad8f26aa40cac666e81ac682e6f8bdb3a94e2b610be6384e4ae50fc9f6f845b0a1d0bae85917e09b477b92330f8753c51b897263f7fb70cf932995fd444b4d0bccb5808bc64a1eef26efa6d4788d81b25b0907881703859f51740a597e22db6622227b454d7b620095757cdfaa29f3ab797efd0e194478618a2ef4974f30b5be7c49b96f8ae85a828d8b19092fb754b9c3c3808cd5377b0a11980045ced28bb1c41f61446b30e5dc2f3a8d7162152c43b6fd1cd009d7c5ab3143346beaeacd1c05505d6fbf51419af0c30673d76420db1ea93c88b6a83d925f259fd8dc980e235887e5deab625596fc63963b941ecf22ca8ce3341b27957336df0fb1fb22125aebd08753c8b3b169a701a5f4868516e13b8fc26185767dc3eae51f1e606bc943160eeb559b7cd0a72f23fc167f07efb200caec9212fb74c61400b24be5e5328cc5f69317bdfa4d79319205715fbe7a77e327dd005e27390b3935c7c7fff72c2b542a8164f07d1de12ee5d95f9cfe115a9d6a6f2de9cfbe718fadddc4c92893a56f7ec4d200f58322bf67f891ee89c03629534d1971f5b5df58b4b169395b5c12a4d3629f5c0cadcd7f982cf13923db111a0ef2cb21ae4b0d96caeda8d1714a49a8d03c6a614d264c53afcd986a4a32e8d49e18b1422e655ffd88f467f132a817e46d89c09ca8d33ec513ce69364870ea0276b6aa5302b283344225eae3316a52f5e0b5a153a41fad6e66423dc116b93c0cbafb8d3e66ad9ce9273d7436ca6db11a730024ee24d83c454644dbb0a2695694b88cf155769c450e1f7ac1397002e3c6580ddb467f20240d356883068dd78f292705bb55cd36bd966cb7b3adfc2f348b7af15bea8cbfb9152cefde2aa6ff80689101278b165c794056e6d526919ed5d7fe895f52883c10e3a072eb7a6432d7c80944db7649c9296f3c5165ebd1fc21444d0105ac9750201824f64ebb6c6a1f3df28406d81885e44e29db8026d28ad4f75a1fd3c3be87eef207457b2ab18fb6658870696281b7acb4ae8e908a24c48f4a9cdbe88ed067c1e448596cfab92f67a48f527f4436ca5fac40381d4aed9386df3f770c5e033370a4a0cad0b8d0134ee6be89f2bacb1d1e93be6c91cf6667d554efe85798ed53656a99ddb68b8d1a2c655b54e30fe57a654d30bc09255c60bda3d02647f29dc01de661d446c03822dc761035215799fe5c72947b82ec6991f498bf8b52c39d2d9af7a0d333a7a9a4db8a7d01efbe55f3ca6fc6184205770241dc0d4cccf1e58282b786fd253b5f8678970422c1f2845762dfaf42f53a0cdc87a006c88800155188d4f53b12ed639b2821f9ce3d16d0f307a3d019f8b6897d157d4aaeebdf9ca2104d5027d2e628f99efd47780f41ad1790a3424952af21be6d8c71db322f3b2aa085f03e3f141ad03fddba306dd5d98f8985978e371499743d8489e6cbb788342a449076f1cc08906ddb5987b8fc16b7764b91f941591bec86ba1bb0c84a46216e33c43f3df110ed9365adc68c74f2249cf7f61ae106250a998a09bafe01dedfc9e2886af67b8d74f3a06656b0ae13c11f5bbe606c01a41f4c3ea6d8a74dd8d6a9def0299adb74a35203686175774f4500c74706395e3f78039d8f069a89fbf040a9141fdbe0f8ae85e10139d2131ddd628d031a917e4856f9da030eddfe5951594c3c73521771b5a2ca8b4f46c3da7a77cfd818398e472f6055273b5714473eb5072c7710bb3c0b31cbaabc6e78cb62223f1a1fa7bc15eb594220ea0849c4068f490106aca11a94dff7c7ccab9a149076c2a0368b71902f2eb9d271f009d8b7e9c3ab43c8288742d4a7f0d074464fcf91e25f3486b60f43982494de4cc601b1edbecce825e5fe2867d94cb5acca213a28a278834f2051d4acef203a927a49bff9941543a23deb91642c34aa214568418d67ec1de7097a43fac7d580c201f01102e7f431113eaa699917531a1194523663ce8482e20e045a65deccdbded21723481a7b1f2c4d61953ca13001ab9999483e4f9c2d9bae92c1f63c15924ace722bf9e1d055842dab8b27235ccde97b2f947f4d2b802d610b4c38817b290af8fe8d7a1b99eb2b3b73035277dd0cfa1af6c9a55389033fac0bfae9b2f4d309f8dda0c42cf8e6b42dc8f46f113e83cf0942c306854604daf5747df05fc3db6379e3c1c5d644b2214947bf7c694244c80f981ee84f8c1c701fd8cd86fd7895a3bf438faaf50736e2971f8256bcedf7204e86f993d95a08c0d3d263c183ebe373fab9f913ce6b105fdc4c391707a526f467bf143dfbfb22f1107d3cc9a7d629ec118a2b91331369ec2d3ecd4d1705382fc7cefb66f707f82bd74ea098dee1e68686e5da7af2dbeaf5ced3ba7d744f127ccde98b71a5aee7c48389239b3be2391a1ae8c74cfb5db8fb2f948a0e31098f3baf79682b878297a1b7c8c6d69d77e334e566c27e3f9cf829a0c16eeb212baf60bfadc6bb78d5cab29bd371166d360a7f3d7bc13eb21584908688807fe393f8e38db6e8af7c8e9a651c255354695fdd0923d873ff4efd94a3f0cbe0bceec577e6d55a6dc19e18717ab674d9c2c4aeccd704a1cf6cfa5d1fdc893d1164763b9f7a036a3029ea8a0efef4cc76cc45e8596c40da2fd9fa173cfd7d13df7099b7a2d7502444d05ee1c7a6de135d211833cb089553d6e9adbc0bd99a47197940f76b12fbd208e25ed3eaa4c4970f51962399130b30f69fe8483f596dafb3b57348f597e2ac987d99e66fa0262735488856d8541661f8365a35d94ac80bf692a0b287e2018a1de2e39aebcb28b4c8752d808e8cf82d595eade70d54bd2e4cf4b62d78dfdcacf54fc534986078f143c55e4bae4836450c6508c83ad9785228d3fe15c0aab87c24def4582dd8e14c0fa721031e2b9b6f25fed0ce03f1b3c8238a1a8876e6ed96d91ea895c1ea60caf1e8c9551a6f9d60fb314925e2d74ad893d633157f5ffcf6e96e7277cc9f550634422fba2f8a609569a477da091765879f0b3bc388c51d1cc08b2e6be077af81403890d15812054f4ef8025d483a61df568eabc74c1efd6d0729d80f0a680cf978dbcbfb0df5f433820ea086296bd0cb10cb1e90bf66ad79ff71cdd0832658a68d30ef72403804a3b9668095bb2db2c954f8eb48af2cca9c297bcbf9d3fea5e8aa0542b09d3491017f347674260043fcb49c1a8f4eee1793a08b057e9fee8834a3cb9b5b89c2f6395bf39ce06e68d0ef4c1c01676ec1829018ef1a586e0fe1f1ce8bcc99f93a5dfa6229fb41690ba0c23f279564a25034f0617faa5a82a4a9af1e262fb79552cc1e4d188ab28df71fab16963bc6ba83b94a8c05a023a3d696a21227c2e782d75172b089ca866821683cb040a0df8841db59959ebd48db47f846235dedf4183e379595a808f7c478b1a669f2dad68ba0a60da90f442450a2e77a86a0809f7551529509190005fa9d6824253cbd731ba97055a2c095ad48c37e3e5c2e8d4999531c40f77740dd2c7d01d3e583742716d938b78a678d77b45d7ef57bef89982e9ae7765fa44306d2eb5582b9b2253e8ea2d1427eb06929546471324ff70b45439d4f63e006ed782b3212705a0ee21145759e7a4fdfb0318ee1218e54c79dd49578259165da32991ad11ee6d052d221b34ca3cb9ebb1fd70dcdfd40bff9564b9c8d9f88f90e4be3c0869f84cce48cedf42c8e4822288456369f4dbccbbec1813c876860f17627af072417817370541f73ee3fa2098940242f6191ae647dc3a1513601adf4bb3bcb1d9339554e62869f9b16e4e2ffecf77f0312cada8410071331e059d24ab5b68b86b2dd5d4baf7e309bbd38c3fe58ee9e4828632c5f1af6260ed12f4f5ff432d175aacd9321100543b5b3ea854efac124aa5188f4d9f0d44c4ba8aa6e4cd383b68703e73842b781750add9b7c5cd5f99aca586dd6d4d4da635b0767f4b525ab0b0b37761c39ab257cc66cad3835c68a2f11740a04b4829a04bdbc398b1f1cd995f9bd68886caa0a27b73a0f470728cd98940b29770e2f7675f0febb5f47715a1b3575e233fffae9ef5b355bedb7a8eca33b774edc15c137f4247caffc17f2921afac79e9609018c19b9320949cbf70123311eb90c7299b9c18c6b26ea4ba8e5b89421df0cb919635f5b755621e4fef5f1948b11cc83363d367e6bcd53dc312421da68f6b543c49cc38e4b94fa1d9e5118b06a7a24507cb279c2e3e5b7a13594ee5ea12fb393e6cb9ea2bc9aa59148dcf90b119bb89aeb0e8c542676a4bfa689dc8d210c22be6e231fe91b1218379c121b3d93d9bc02e8ab7a0d4cf44424111acc3a06ed9725f226aad1e1c5022c82155aa0a8589657e4170d14d046e3968d754f52de66d3bca71ae908faa41e1667363aa469945681f90c056c1a1a88cbeccfd6900f8a5d8810656935b133e9d793dfed61a0fb5ed6dc89cff070e51a8391064c19ddd7db008d42d5ae31850be391e12f5bb8ab07b24dc57992131d3b2769088a0171b944acd889d5b8e5d6034032575ec363571f08171e1382d63a68aaa34e34e4dba6a235c5282e86ccd152a310bc6fda46f0b832391a00d346661625dbf2ec947552f0b60805f4e241bd8da27ca947d1360a7ed93a0154b8a1df862b956582da496bbbb9b40eaf6cbd8aa850a34e93a2a28f198ddfc6531b6608d31db647e58461d119a4965bb775e78730d84e34f66493d6713bbb3bdeaa76f00cb329d60cf3ed0e72d96cd87a96e03ed84f1d0331a9d834231d73ba33c5bc907e45cbb53d1d68467cb1642b0e2e17e241da46fc6ab929a6ccd93bbe0402777d73bec4ece166a12ec7ac3ddc07de215cbf07d04b72b7ce126a49ff1ad8f99a4959c09621112e22c40aee7ebda34385a13c4a98584dc113f76e7285c8a67fd3a9d02cbc538cdf3d9258e1decbb2fd557f5dc848cf2bfb5895eae2b41bb2bf82ba40a5bb7da04b3b0ce78798f315bd5a20557781d8099176e6055c5bfab0d209c8e66684c55f72762dd105695e30ffb2188df56c6f762255db64201605f029af0609c2a34a916c3dc846552d63b8b9bdf406b05d4cff4bebcff848edfedb46c53572501839f55df0a1da955ac321234646b1df46f3a3115774873e660425a4a6da626a85de9790fa4e1dc45713b1e4e6e963ed3d4b0d7f19a9e9833e76cdf742b710febaa9d82396f6c3650215492357f1a5adc2e32d085b638c1083df91a56a88bdda492be29bba523ae333e2cbff18ba81638fb3f53c3e3ccf3667e2072db2c2f1b203da49760652edc0358f4ec0a4c4e897871c94be2a2207e666c0374b4b7dece309cb7f5e9f1b77066bd63ffce669c42e20752859d2496f1251e48290aecc12cad22123b3d285e5e1de1068df6455e8f6b936a504110a80c737005a5c83f7e171a863a8e5fd6c2bffd770f9d52fa46235a85333b90b32a5a0d2f5eb347983d37b25d4c085f3845cf435c3ef0b2e73d6eace1d6af317f97921d538c92273014c1accb17c457838963b1fe5ca442ba9aac261448f761a3e5bac7b72d319b2293b43f4b0515e9cd9e2b3c469826dff94d4f187d4410f1b0d8c729ad7c6b13db089a7b6023d680c757acb445c90021b6a5ee041a7c6d62803bdd25ac7a5f72db11441549071f00a57d74ea5c857db84b79a67b510ae61205f1a8da57c9bf366ec5487c15aea5c1bd249a5c155ec4aac759fec30a0ea47053a395790d230d34e0c62b9997bf4773d214d5b6e1e70d07772373a1c1feab46fd5560e262ff94d83633c5ad2da52d4894fa01ffc80b23fe86547c8593ec72b41f597db364a4c8c3e6eeb193622f463dc632d24c275a24af691ec0d04b6e05ef9e91ca338ec567bea3ce546aab50691f7b1c43cb60d985a4430efb8d839c4b1509f1ec140a6ff3f57a6ef996c35d757bb44081d2cb7bf2235bb9f4b3cc7dd8c60629d1b4674d242b481127b6f589113a0c51fcd412f98333452572c270f4c1e81886613382711bcf1081fab107be3d8001f0f104ee103b3dd5bc1bd7e8f9dbe881b7b0eed3b1c7f4ece6c8ffedd0f1fa2e81bd3863c6c132c4db4e18f7c7fc417b05d0ff434f51f16a799638549bcd27e6d95d686d65f532d66f91cfc325df2da24bb3e67cde3d929a94dfd68c23bfaa5bf8e163349abfe9ccc9dd624fdecfc855c66e72e06e3751942916329d2a38852af78e096bbbd6c6566c80d85c861e142602979810be8054393f434ea569ef3f27d3e40560b5bfe42da80b00fe367e4f42cc35d8f2d8ac55a136f14501a191017418447dcb526fb1857acca78bb473b1b9c47bc7ffaf5e374ec59991b7be346a9590013b11fdc7e021ffe42a6023c66e371afc5b0ebc73f386c5f575972018290575e4d5d467dbfed252f25bab5c6be00e40438806be8a282c2cb3a29dd2be18077f88ec320cf60c37cc3687b724e1d07f5ca0b480af1c31051036a32226243860eb2df662ff24a70a0fab3ccc41282645a469d6caaadd1672511d6d017ed4ded0a7a4d2e7b94798c75a61381ea3f1b3304a06bb2fe648c312611daed6832f5509eea4fc89f3c6b5d76c0fdf594c8c7ac079433bf1105dc9a919351efbe2dd0e3061f76a26e98bc02a9cd881a53d77df390ff3dc8b6f17733b205bddbded023916f7ec179f8c4e0ab197b843ce84990b6fff69b521f989fccc0ee5c1a57baf854905e1c80d5fc40fb88d343f43db4988e7be96bc3ea01b9adbaf9cc2145eb65ff9937e2afe655b8b0f749a16a5ff55f463f16f8e61c0a3524d06dabc0293eb51e4de36ba108179913d028f32be20756a8dd815694dae0b9c20ce2b40a504d72c39960c391d72160e78efa1acde387df41a7b19eec275f3ffee0085ae485bce25f057d0e073cc0ce80fa26388cd74d61c377797b8b91a496f9cab7544562a0d320e84d8621f237226f4deb2ece7e41becd375f7aae7a790b040154cb5414ebb88d4141061215d7adcce7e230a760cc58a80ab6b081f45800a4400a3dfb5f8ae7fb5b52f0a139664f3e133d2ff73b04e8074ed5ecb2c3d01a15bc8260e6a027311ccf77a19fc544129b626b5ae37483f7912847dc9de79e2d33140fefa619311b3f946ccb0ab7e8c1268e611ae7cbfbca1a4fc721c81f92fe0b6e4639b08eea0ce17c07a47b9b5e2fddb23d8efc6f4bca9d70177d35f7ba87f8ad47fb2e181a4837a040f27ae5bba43d11a3a09e726289986c5589e2eb8301fc740f121c9e625e1ae16c9d8812c1e3f16e5f126d3e54937b1188481cedc491a4f20f0ab53e9a3fdf3e889a95d27fce2c306441a3145dfe4235caa7aca0f84aae9b0b645ac3f8c5ea57639ac900baa6534575766c41e47f01d0447e7261cc3e2fa03507eeb1271ab88084156ebccc1da8f57a2336823dd492a6ef5b58e0f9e705c54e8e375137c4f3ac470eb06d32b8645dbffc30d60648abbb6b05cfd42f8971a61323131a28dfbef0e142a4ec3849eba6a23bfadcc43e2c8f899394eb64db32e2fbbf84b8bf382320bd5fcf02f332e799b0cb7190a229d2b7d4da03a3b607a641e0591417fcc238a2e43ea6f9aa4f12c26b9dabc15d49676257b84bc9ae629caec63434a48dcc808a9b44ca8961f6c279a2b7c051fed5c662a3edc2fb8a5b5dc2855ff89f1bcd20a59d9689e7e454af4e53dd392aeb62e2454b5ef95851f36ff373d9af87583d2647531e3c20dc79b66aaffd14e752bb36980e3b05718b345724c6d30999cea43b1ed3ec0f26f07394c1b630b930f07cda2e955485cda93695285bd04eecf7ac06bf3503b61517713b0426ae73bd101ea41ee1e312be171705e492af9498533baf43796ee746074de1e6201e229b06d894ecc79188d0981744cb02ef8697c9e39da2393c9266cb4c03138d5d512d9c2287bafc626c384047504564988c0768274403e79f704785a057584eabcaaf64cb71ecf7fb7aa77eca82b873283ae706042b422d1490b8ae3d981a0e980f14b632b463596170c0c03b36f926c85605912c6754a7bf0783162815fa52ede6e5d20ec7e4b2696827810a53e41d8614b9e42d14062b3f0e46e3d85d5dde7d4c6d75e5381e02e1b43451af171928ef097b2d75d1ac60933edb2507a7684b5ff01af065a666b003dd707375fb06b5bc8f94d013eb4aafff266430c15ec17bee57c4ccbdbd8982dabd4d974c8f5e3a56a88c7861561af906ae845b001464c6a14b72a562453b53598d93c32187bd9902ab2f88b9f7510772ab329ad4bfb253625efb6bfe8345e1eb6963162335272f9d80a423621618daf67c952aef643c1038f1ad14677fc8d1e1f61e49e18a75cd5503dc78513d9652288c3a1f4a7f1c0211b0de78206f6249f6593f746c05384de2e4db38afee13c90d806f14222f3dbe5d4c43d700b624d7fa06f41fba7096ebe913dbc8e26f34ea4871af20c7cef5242735e4013bc7103bea6547c133c43d536c0786570dd608754d0f8524c043e6e72c32a51de52a6cb25cec3fbf1fa9644b0df8206743945b6448e904b0bb6f96443561fe092d49d1ffa3b88da9622267c320719e5c425576c184325ef9af33de387be8e3b25276b4f37a2e9075b22713a85558ec683bb637da4395327a8ceff9ff7b9e9a902e4512a49486545ffda3e0de233aa1173fca41c043a87b2434d49e8b09efdffd787ffd8493bb85ce498758a34963ccbea576af51545c525e2efd1938f3d993250416981ffeaff8ae10390a851d67607445245438e7c54c44a3a92507c84fe15d4332e6e81a117eda373e25752bf2bdef85f14266b4437dd96fc640c301bd923e7cba4b506334b2f78306fee80fd81b200949baff737b7c2073945738e24f707e99f597c3d5cee26aef008a40dd6350e03ee460caafc5c4d48a5ddfce7b8349f956cf7f052f2e49e8827aed54612b0eb9bd21bfadb7aa1687ebd0648ac79a1b83c86226c3e161fa6d5d02608ffec357e400fabe3ba204bd7bc3e133a2ef3d97e6ee4b69703bb3163f01c3c723cb7465b06b7bd5ec732c7436b6ac21e2f282b2fbe931d53167ddd9ab8ba192a6f1e962a495ea61dfed91d9cc836eb6723074b9a1e6dac96171d9fa44ae668fa77b4de43c1e16f84d8a034267229369ff9fb2efce5ad0667baf9ba2bb7eaac153ce8c7e040528087ec4c8a75817caac8067fab8787eabca41ced3406cb9ff80b85e238f16c7152c223f8e4280a11571532916d57166fd862383b33984fe3b1e2f92b0b124acd4e9a27f64ddbae42fbb745b0cc974d94953f5031f627073d400437ee5cd3d6354b9a01141ba4189807f8a361923ef92f3022c8328293df4c5765294161fd23ddc8f3760d765730101f631348101a40fee4fa8253fe24bdae81a5cd1036a13c13454c95d1296b119e56a130557071fb82eb68e21184ab3c68901c7f5c69fd02ac0ba8bf374b70254d7281fa78e9b283e893e3f20229c07ccf61f91f9e3fd974a205070bec5ae5420254f11b3bae2e37608b6323310e9597d7caaa982562e7872c46c3e8e04825c4caf586950730a1ae84bc23789519c98a4e3433bd35500699b839d82184e78a20de12d08c82045f98503201300169fe021767e9ffc5d111d9422d06feb7043fb01890cde1c672787b3533bc9760e39899c6500435e9e9c1550ad122c4c85320bbc7f9a2b307338cfbd8c8a1621cc9c5c7072854aec95c70c5b59689e941dc5891621180851d73c28d660688e2083914e5afd69729368a174d630514956ca1d39e1eb8f01ef8f3db4ac832680dfd1d37913729825b8a4b15cb567566d48e4ad7ca0ee70949d6c793616340870c57f2380db0baea043e40689f4e6a1a5d60f5df0b36623d2e95df9cbc003db4021ab095fd51b398131bbaba639c5e07c63a04cf6b68efca4857047ad8abaf5b39bc1707dd28f8a51bfa08104a25d7ec90bd47d08dd9c1974f606e8e5e55678685c165ad564a38b04468c405c721c6de47a105b65376b5b20ea8df6036cc5d7eedf47c6af4bf7466a5b84f803476feb9fe56ad0baf8a8f3cf82a1a74d1c08a56bdd1debee69abc13e060fb2d7d57ab439add909400ae475529b82bf560ef3f3691732feb69e8e6db756a7e5edb9ce6f9c5db39b24f2455cc9d17c9e998c3ab0ef447c358757e10e6b8a062024767963e689a22b954d62c05ead368df855fbfe20f04b0f0df5babab360c9b33fbaa5ca7904b2c90118c629306bc6097ace3fdaa1aed01d3d3f50dce4e0a22627de6b3b0c41e39594f580fb23df64fba7409133f2da663ac2071b1df332f1f2bbf41098e8149121b8270e9a9d208ce2612c9fae200b9fbaf6ef73d8270b4cf169885a4fdaf35a2a589a6e4696b01ccd6d97ea7c4026aadccd6602e143aa56b0b3732fa02f669caebc45f2c5bb7d3e5f28814ff2fa22b6baf68be1e9f9f563af921a6ddfdc805591cb5821305229d6cc404225da07e14deff88434a2e29daf73ee2e7d982840ea13ac4fee9578e746847b4ad5a16880e30b2c218041967403a64eed59d2b17085d4011640d98bc110e71761c3affccf5b9c0c2b77e6cab473520f4b80e5bb4cb36200e79280907527e62b18cc651970b1848dfb282b3c3148bc4e600bd14d2dc48b77f10efc228094e19ed576298183f25e03986d287188876bc2f582294f80d33524b6c1025a754ea78c4329c5879937d6c1a264f6abb9e52773600547468daecad946e47e4e4232f09eb0705cdbb102380ecf50d38b4350791e6565b090cd6528698cd40da3dfa6d4952ccb2aa44f81e13402df111e0835c833eef193b3e72fd0592b22bd0e2dfbdaf781399cfe19dfed3eb8034d9db54437fcb881770e3e3f160148d5ac0cfbceaf1769da8f19a24a0eb92f53e931649c05e0f10d9164beb6419e07503c0ac9c8a0d709e3d5ec654d922996bc01eca9281f1e2072709c6ffb02a50b8cba467d7f2f7a408f8f7b482533e93ff2e05877d80b12aeae5462c6d6e4804773c19d5357377e2512b0ee0a8a2e6571fb37f0a163bf2f4a172b1fab6de166b455e0f53ef43c27b0995272fb6ea9d1ce6c8a807d93c7b4e240a5b990f446cd4588f04792bd497c278d4bd1d52ecb612323b85ea26157957d02e42380eb4a8a131a2b3ecee8f142b0908166c74bf89e0df4f81864d507d297207cb736b65ad5d2b71dcb8e5ded681f2a79d2cc632a14c21ab73c5e5aeff322ed440038b20d491270a8720c63e9cdcd77dc61979787df489f6287db54c5126bf40c7b000bd3c854a3694c9057f1d78fdaf193478124ef9b7b47906ea53506ade3757055b9e1c5dba74f4757e43a3ebcf22dd210e3ce6c3409bcf14ad7d0ef6d6f03476770a33ed4b8e647bb680ad49065501e658de95fbaf95dd88adcd8eb3c29657e97271508fdbdf64350fed98cbb721a8c282b62f9046a0434833c1229dec7294eb3d88da464b0063007634e39477ec6b777c0c687d2b997e2561e5f89ca582f081f0fd68b8cdcb1eae88e8f6e6c2a6eeea21f704103f0e77bc68543e9b046f7d43c36c51b35cc8323397ed3fd9ede34aaca4ff9ead45d04fe619f3dd0cadb284364fa4b9798a61c5346316a9fa20bc92027c4de6e98a0f9a819ea6bef46743e76b94b18f505c4612a233d1be297b40ea5e4b67bff3135471f3cd41907c35e1ed1475fd27885fae7e18e44a422d973fb2a41246c12f2170f1c75065c27616f739ef0cb47bf8c173a7ca502ae31b8c96471b76e17e549805ffe42ac4e628b73f143a03b55ada5368753ea6ae59e5a9b063b5cf8a5812da2209613ad8d38a66d3e243111d494efae77fc74e196d9db1b59cfcb3180606492c8bcbc8a9d8fb1b3f30d1ce5085b04acf4f8f875a8ca20002cfc6a51ff7aa6d817b199ef3e1f7fc1488441c5130196a035da2db93ca3b32cf8f6221096f76d7a621b6b530c3a4db880a123db4a89a2afecb2b10579a6dd3d3736ce4e1bcb133ad105488c8a41aa49f1950f14f240705437b37aab45a6c1f8099376308dc0d5f10005c439dd6a5f23998781bdef02e0d07af9d92c0e220c8249a200c2298c8fc0774472cc7bd318e66859a85151a9a1fe7140392e1e78e9d35ccf62839eb670640d141abb5fd5bdbde5bd2043f2a23c591298bd617417a672024b9d0d38523d84441595fe113ba24923f0824e7f82bfd502494eda9ce4aa72808b8152f6da327af39207cb6a1c36ef3b29ef7644fe9bc80f191647ffb9a3e237c917a37e4f464271b939c435319a9a74afb1f8ab879260d061cde37589e20b610d0544a468d64ddcc53d1e5c43571324e9c5056520cea7da0d2abb0d6ff0c08219defa71f32e68114ee41ce78d41aa53ca41cc5a379492562584f61be32fb2953ddde0a9eda3988272e57860ce6e517132422249dc51f1335ee4db3cbda75064afc01b132c052c6b616964d68fd72500e3e20b647537a5186555703a3cd2ba9d89b60e93ea17781baa02293e32a7cb68e974c789331b467a3f8070b2271fde356c57764192080525d8ba47f2a861d43941cddc40005958522bb58f722c8ef687de42425ea5e3f474a5f31ead4cfecddff91143eabbd60d49fcf6ff30529df1abb39269919144f7daab86eb2c1f652c1e1ec36fc8a0a1e565e6551f8fb808674ecaf21ba3d298e8bb286b6e04c9b9af4faa9505839f8ce218534a80a7441810a06bc9e6a6352d4483f66132e517de0422f4322123df7668f2316c528675d91d59bf0c25620ae1a9141f9804a643923e8a05f2631d1a37f60e947de223b71579922a9c15872eed7adc329b3938789377a3780df7677c793e983faa7cdc09a682794238c77ce6444cf8594cf1be37e05535b6a1742feadfd6f93f08088493036536fed4e16bdbaa889db5c8e4b00aae121669d8916369b9721f3e065eb31a5688f686afd372e6946005140f104ca0690d879f2ac1418514d41a055a1474b67b4cf7d6818ecb8a9c4f3dd9caffd5fbf3ac75f4498f27e683c6de14b41e19187e403143b07ef73a3f026f26ec9558c847ffd6bb8461baa76390540dfd3230154425ed20be333c138c4faff5ca4070325948310cebf521cab9e246e1a0fd5f08094f7b0880f27a5192ea6dfac598158d83a8f2b92a1790ce91e1a00dbfd6e68d645be92f5836dfb488a8b4fa7c83479ac437a08c9261211cc3305a614ecb17452515b9eb16ec841268a51df72008c086030db09f83fa5809526b327ddba7df2c4292511f739a021acf644429434541c1bf4aaf8adcfbd66e107a3252c9234d2222318bcffa86fc7cfccaeeabd2454986c1358b3dc5033380145719bca58a1218d2f0d9b592298164ebb5da0a963dc90634ba69d6ced77ed4de61479303f60271cb1d8a29797af95117efcf9b892278781b93f4f90aa6a21b4c9dcf77edb61dd73123a71f1ab6f18b59f6eca265df06494d08dd1544a3df34cd0079b54bc44c3860c0dda6d119623294bfff953ca3a0dccc8d65530b7b139ead8d8ceff41f495f515e19da8f3ca33bba07bc12f965efbab71049942575b8f67fe53049ecc40e35de044b9c3596f4d6e7509db4acd93a49a9543c7c9d6b3681195aaba1f827c267d89b6d4270150ae8992fcffc332dd9140edcb37f38e7de8fc749c222c8e069bb730a37d3f282b37d8fd70461f08f5543323af7ca1df97b11e61bd1b7f2ee0da1a7a2273d895243924268673ddfb441494dae9b0bea97f790e0b11b1d0cf10fbf5142efcc38abda52c4222cd0f6d68b3cb5766652fbc4dc74d233617f6caa438bede6f8e6fcfee75bbb0770bbdddf75b62b5a7f5c886324643a4d0d8d30a87fdae4af920b87f256b3c638ff8513701498e0ff57841230b2af326eaab51ffadc7178333e6406e42e46ee5234774a7c2211f9b1a3d1a718742b1cdf410f8e8e7d0f69748440b28150709bcb1234943641d4c254c83c2fbc45639f76028378db8e6a3c1430edb77adddbece43552ec318ebdd374454fa3b9e07d307e7444d05881a145572c61cd12be281de4629e1feb2f0644f2658913da1f7a640624d5e7cdfd9b1a29c40809f38938095e0e486c7287f4edad96f066deae93cc66bf5f444647cfbdf3ca959444740d14c780d7fdf5bea8389d5b3cece7d21b65c282bc0b26c911af5c8e4082515c6dcc48da98f9cb8d2dbd795edede41a2318f3aee4466a7988797ee4c51ad63956b431db2143329a45b0eadec0cc41220b770621fc287758795bf9cced49a0b401627455b29c6c0160d1d0486f22bbc12c3ea593a32cadd8f8052945c8cc35622ccdb0e954f784a4c1da556ce8e0c36422a16f225a87acdf46204cd5c53813bc59ff74833857807ba3b49306d23d16ed6f93dee4414c38d0288da45c72d6575ffac21403e00c43127c8cb7d5e4871ef7eef1ac52a10289b5d062081b6352089ea55760ddb14c44bf1935b4244ccda2568a68b4db55de58055ec0ecb80df12c6b9586e65fac439913d66bed6f9994d98aa54d42357cbf7ed8ed6fb2d2e1c4740f2dfe084fdbc27fca336f3b16e42be138ed7ff4c39b98538fcb9b8ee73ddaa0e356d7a5214608bddb9823e8c2037ecc7b7b4ae0e728a2ed17bf062f8ef16d1e08f47489ab86f4cf73ed09c607de1f83dabce998cce409737bc36dc4104d41d717bc4e559973df7d4b0684523ea0e76e5af50ff4b4ff7429d9d8a76717fc1ee0d986f2a7e57ed7c369f93dce329015e3ad309871c6b2a75df3beb7e6c9851cf26d3285370bf65e98dfde5e506f3dc5f06221827528284c43fb0957d16facb0d3330254bff36968fe2067196472a6f9bc37353f9a2197c999087c81d4880e0dcd4a0a794f2da3730dcb98973daba9f2416e9530f96d07d80b8a07ec873670954f54ef8794874bf6dc8afff935408f063a05821d369f3799152c7e2c8438489b9c7dd6e10fdb8062cfa9e14f35ed01f5bfb2511d774f038e887434184c7213c8ebad3e99ae8896b1790251e59e1cb80bfe6ef9a0ea4ede3f4f9c1ba1b779d8a99a2d02ab7c351054e2c7885b5fb6b9fd4e0aba1afb807968c5525d0b3947763620815ba3fa20f9c25eb96fa8ac562c93595e17a8b6ffeab1d23aaa071984f2df310e5c7d66d980544b46bcc9cda7a24f2d639b802fe63a10628b13dac811517bad9075c00eb0589b989d907774905dff4fb5bbb9aba43a2026113089a576c9f2166fae7253b3bfad2bfd626169cb665fe528ee582904215f6070803642eac9233464244e0fe40b67104e644c62ca2e6d0b5536d0cb24d7c3ed6f1e4ff52206b7c20b0807693f673272e50ecc437465a0fc66a0b3d80881a858bed8c660557c1448294e429acf35ee73b0db4874c7faafd569327d3c08d20889f39859708ad3bc7dee0ae344893d02cb6f07391dac61d3d25e0105b06a10b46952ee42f54d720b439614469dd42a4ea47e57a39a290a47b99d8e22018f311d8c4b0e12f13fe490ee2c776b2e6259644bb62925a5483be119a0222ea0cd800faf6c1022493da37c5d938ccabc9be25581a0d5040ef5509cf954851574aa44d5279c5b712e06e0c42d06b3b972f02bcaf83680843ec7c5a9a797986ee27fc8f138e37a9307428c850e7605991d5e978a4b880201cfa5530ad145509b0b9a2668f7952fb518470200cf111ee61082fb384c20aa41437878b8e148403844ff2717932ab04dfc12ea94a4b9e10c4386746f3186184d9b3c8702e3fba2c15661f6eea0cf0c13d889d39b02770e289656ad1e8814d19484435d38f801e421041de86da13a865d6743f557180970ef3a7dbe6520c338161c06b82f4094bed04253d3cbd142a627e281194b75861129b162f3743a6a3370fb2e11dbb15d3bcc8e3e6aea848accfb2d4e385460f83df28e53716bebe6aa47df65393390ec4d2168bf5e6d5a1c2be47fed69e54badde7669937aa96b58cdd59778282b6df1f0adfb89f15133ff844ebe3c8d7be3b0bd2c1f5ffeb5a5f72d07259de2e39505b2c46ed9485bf09181e20a7f320911b6895cd3eababc22ec56760c8196bceda2a8c93535030f08c08388a64d433df68a7ad32c0764d199e4f914f4b073c0ab59602047410fd8ae0dbc02a40376ca1282e99d0847190bb2fc2c6455f04f106f712f897e841a1ad5b8b56c98d540724d0fd3b188f400b1dadb5cabbfe4cd2526c316d7b0f625a3198973972e6006ea9297ed2064f3ab30b3960f11dc5761a712e5055f36857931d2ecc669d00196a5d9a7e389c3c4e72f8f059df7fdc47d80f6e1aedb3485cd5cac32b85c50f801d3f044cbd3e91b67aad89620e55d92aa5351f5da9b1efeef4d169592ff577031ba36fee08f5212f0ad251490e4a1ea0eb9d1065039e4ce69ce594c7c45e335dd40a9151d4d16a36d5ff7155ebd30a21785f43adbc0051d238442eac7801656eee7cfbd91a96c13f58a5fd880daba2964df6dfcc4675b54793bd93d1ecc5e6bfeb5ceaa9b729960941feae58277dcd8c7028b93c2becd2235b23ae1a33586d00591504e84c9eb88ab6cce05c3c3b9d9fd52fbda6974633a7a1501809f0546c100b52082c4bed0e83571e5dac848ac26142c6966e1363e1b56270ae429ac61497fd5400aa45916af4499d401420ba2071835c79e265c23a3e841fc2274d0838dfc91a0752c33c44257a53512e1216e31bc2d35fbfc3ac779cc0cd8feb0faf9dde7b3e5dbf04f05839b94f1bf4f980ffd6abdf6ac4dabc3ad2bffca0618c06d9c39bbf34a2f2707c545e2a7b70238cfe40ef65f37228705cdabf5ff5224f767a043eae5079e7d6e022980d176cd1c7e8c832fe1e582645e48c63fddaabe469fe8c4f32cd53fa9ae483fdaf9881835173110b114aada27bdfd32abebfedeaaecade6ccfa88f216f008a807a3a05e5c237071569e9ce10846ad8d39668abbc87f1d26c46dd125523fbbe54a296be241eaefeada289ca89e42d623ae5343d13359220f0ca3c00186c1f2f5fa4ce62bdbfa0bc0bede6d79fd7048ee56f1a7036c9e31ade2a9f165071008b259b2a6ec51014b9819e28926a6f754929e8d7c03a8a5943f55a65764d637ba2736330a45c3c2d68d450c674e850759536b415dbf21229fb6b5e6fbfcbaf23152dc677f576bf7ab2b3069b4bafefbe518a0bb08f73bb5d8d07961f911efa459d00fd7d46f8007874abdd41bdd1beeed82440b15997055ce0cfab4cea143a63c4779a043b1a6ce25eabcc40cfec7ce6cf4d82a53362af0bc36323303a02b7d5a08f97276991a6f9315a78b13037e22c16a1026b7b8a27c2be3d5e07fb504f696dffff208aaef634603993cbe15e477a81f870b0ffe7bdf404cd9721064953ab5a7fdf4a003beccc669cdcc0d39523f66b16af541806f0b7373a22105ddfbc568bcb7a748d0c0d5ec59e454be6ca7ae3be5e647e6f0b3e8273a22b7bc499aa5e4de00636c5e1398f083edb563ea176ef804dac728489bf64f74c691e51275dcd3f077ab6f0d6f7f3e00f777e94020371353d77c8946185821e4dbe95bebc6533908f669e2ef0332ac72bd7b66d923ded61cdfee6f556627167a21ceb934c564e10a4e9650d4bbe49803bc34a138308765cd1b5656a01d37e705c9dffc520e9f0f073e6bb7b1512c865068b108c704511fad9f0b9da3abc8262b5a14b204e0f4699e4cf2f12e4ee1e3b04b8cfa193cdf1dd6644b1a76ebe97ef9d42b5a4e2f10afb93230ff8e323c79670ee2dcdc363a1a7a00fe996712304b0dbd3991a9073907efec5489dfecf7053da6c2387357b247b8bf1a7843787cf35b20e00f93f81b17cf74656110f9ed8156cd7dfa69b9165cc1e3d80e4afd8abbf045e5e88e311ac60982d2c6fe260705b6f2b2ce6c95cae469ea607dcb1693f0ef353864881b358ad69cb1a02d6e3a074a313e61e85b2123a7d9b47a317bbe2adee20707365deb11cf8b163b8875529fbf6ea49f32ed72b900bf4dfcd5f7fffa94d5357ff6b108b7f7e903925aae9abb60cb6a4971bc0d894a82514fa450bf4cebb1bfa1ed97a4bab071b157fd59d0d9172054fe9f5884116fab3ce6dd1c13f3909709f3519578c2cd12dc821f06dff380a35257f08e728e31e121052432e464f655167eb2f43109a8be991f93285d077d09187c2803d07082f8d29e08106378aa970d084c800faf4babebd70dfbdcc80ef7af2b610377e6b0bc551c7ac53a67093d2277c3776d4bc8890edf9919c10fd41843f71e9c94b1840494752dd09e5c31588f254197ea02b35c67e9d451874829b97b76aa73548b9824603e25d87f5300f53dc9be76af1c450c2567eaf803b27ded279713f7a71054e7ca65c34a58a0e9214d40c3ec72499e668ee4f5cb64fb970be219269f4cc7666dbd87e43405f9cc21798bd52ff123c6f830a10654197852f55fa468d7c77b5cec7542217e977560664b991fcabf6d31274063c334a1dae7d522fd1c4c4ca4c250122e8d285c3365184960d3cda5f0de115f6a852e62883f7b9f201de530320eab0447b132023903ab0ff353609ef89e20f01afc47340efcc0058e006b4f331f3bbf1b27bd1535f24f25d1fd44f6a42514c9fea85849860a1a617a4022a98172dd3f5cae06fc8101c3400d4bea69add08cb1424db8cfe756fe50c7aed46d14a8521710c69e9b6800e22f1c416a83812d0ccdc38571049ea14d96197e65f33d2fc89f09a683ad9095a263d54787ce7895e30cfb2db6642755e35d01c106752682d2421e55c7b0018af95a845a490173830b9345b498c7d81f16710f53d9ace25f17615601ff0eab9a2582a51bf1aea4116690a8ffca42287ca04b799767a4cdd33f166ca0b85f350557fba1130b388b32c859c4cbef2e18af8841e17c1f20ba0bf70586293601ccb1aafc904aac9461a29941dfeb274420ce27604ecb5920fdfea5016c66101b5791f4edb7b7597ece638f004f54ea1192c4154600688577b24d85e68713f6d2f7ac1679737f9a42b0f86a8893341326a0f7781c6b9a2a08a026936bbc420f51b009eeebc1ca65fce50e079a1b19d531e8c9c7aa441f65de45b1054b0a82a5c38332a060d8e390e0c72d208cda109ff98c1682831630d6d332c6e9f7ea77cb08175cf0273d0378c52214ecf795b71d7d9be3f4bde1db57c5b7ad8de416be583d62a2413c93f7a5d10f3a786640212ed391b0eb5c9acaa96e1d773898c20d9aa7f4cb477b5fb4650c5382e25655235dbd817e0bd0ec830362ae74ea4eed0a0e9f4bcd668021daa5f376a90a77693b2c95bb5376ca4843bd0e11c6b7d5bb67c125b88da3c88a9013c47e87c71575918e919d3a53e58751c307c15fbbb619d6664465404fbde6986774495086982e307a550ae6efbfafc4eb06a44079cadc7bbb4fa5072664e45688a16ad160fab40b55181e5300fdfc98f74e19299b0c7943788d13e13c771f26e8ad3d3a7d0fde6701d903babb01e083f61c1e539eb0270600cf80ec81e5a918c7de9e43aca9abf46be0399ff3bf5c203138c778a1e3615209daeb25d83a5b595a828e597f4876a0225d80be07cc2497d3cf13491f5fa3f8f1bc0d9eef06b2b6333070f5b671b0cffc20c57fb3cd480ec36ed7b9b175612f37617f017d036f061562dcecea863d7e2a896d6b37690b744fc1ae6d281f25c801d68e275c5552781100743121a644be5c9c7387161c6348bfa0ec16b0a2843a4d3f6101f2b2722a660b5b6ccb5fcff73b5e7fee0b4a709775575b840556bc18a5c6d584e328d13f862dbb6bfc9138725ed9f3fcace5e1a46c73fc2ed434d19a40373660fe8c8f0dde0ecc0522ecd67566662cd8157d2e827e6ce967e4779473dd38ec3d205d7ee07b0283f20a7c8d5f65eaa2a3120a74903dc3ae5846965d4983dbcddd87946c7a656ea18f7a918ae053b571284a1a00e61cdb81ea157a5e35fcdde6907ab698a167f3e3f548ef56d7db539f022df5608cd0dd2f5d57c0b1646918bc30cb2dfd1e09312ce88acfcbaf918fc54c32a875ba1c64fb5f7516bd4273c66677de86a63adfafa1b93ffb5849eaaa42d6107e5806b1dcd6c61c0b137ab9a6a2ed9991cf70c2ad90b8a1cbfa375b7fa74b6aead2958a48e49f7e5bfe61c96fe1ae7641932eae07fdf8ed0b248e0c3006a9ad3b8eb2aa5b6d464a7d4c21c63ef2f973e8e792f7a4494a53756320325e14a455700fb9b5a5ad10967308f8afda1768f38e316dcda33255c67d2fa1469ef2cccfb7d536570a7ebbadb064e276313d1a37729e38299c9c79a06286c6715708078729d2d7168f6eb0e575c30685ed26731802a74b9de26d84d6d77095df099dc6c28b42bffceed74b52a178baf799b0bbfd921c092dccefe167b8fc61203d05f21cc30c36136b36fb23e5c862c00ecd0aa821348e059a1f49f994b16d6166565e2e2bc21165e35b17c3c7371683770df6c9659c26070d977a17b50dd5e5852cadf1693383df9e7c9ecfd32733c045b69d7b57230655cddea70ffe92257adb93a47b7436eaf3498c2e235e9380d84174ba0566828440a756a70f956b2470c803888c4d135e6c37b280a2f15d7b086a8adae9d41e6f9befcde5d347d4a287a84d151f395d157812d4c6a7fe042a0b7b17bdbe1a4fb1ff67b30a87479f60b32caabcdc4b3b4cd6aecdabba4a0bdcc7682edeaa2dbc0663937aa0f843a0ef289798b3ac09b8040001a702eec33d63096337a317077d50b0f317cff0e9249dc64414a50ad346b4914a70f8a5c3c44698f360fa7ad60d71d048fc96e05d380879ea44d36664b048c3ddca789431821f64876fe8464107c26368d3677548b817f38a71c2429c00fa2e1c5f142dbdd080e0d30ec48af53f3697cb5b9bcc4301fdd19a330064828d2866d7d5f6080fa83185b9fdc80dcfce3e183bc09c22e42a47cae0997841cc21e9b9ef9781311a5c12f898da8dd189bba3c67d2cb1ef1c3c6f332a4e6748241fc1edf3f0c5241126abf69401d0329742b13cf8eeff2d34e50d9dedfe85754a4d987aa9a8a582468c3e7c134be0b626eb4123d1fd0bb15842e399a3d36fac08a748c7da85b5404c6715f6d2b8c22a8b51749291e33f7914cc3aead314f694918c5fd5c322feeb7fbac2d1fe761dd0359a3f12c4d984561a2ed4b0871fd469f25ada2161305601fb800f1309abf4c0298dadf48ea864fb84e94cb63a70f6b924fff7a9361e834d3ec24a8a9e809aba2567176d370e8d089d20162e4d6f8e1f9c1162320f300eac01768deb6bc4f367dd28efffe1b0f319e2a8b743845fce0c421986c74f165adc21cff64f8083a3afbaa0f1e811eaf22e03abf50ae632d2166bbdd9f0f82b58f3adad83939b832a43743c319095740ca242cdb8153b9b03a8ed09271f9854b260a50d62749700a2adb04f59d1fd6a8f39f5ffb5e547eea98b159732320b5ed840afff3ca5cbc8d6a531ad634a5205b26f4e66c90d32ed6f22be7df9bc378ece613d5b02347d1619c87eb809ae252aa6294628f9d9801a28c8b90fbd67eb7855227194d483c21a471743bbe688d383a4075c295115a0739efce41b151b2edb18f5947e6098fa02d9f8d450139c0db9f1cf0140ca899693af7c10c50a56df6a4f9998b411bb74c558f372de993f5dd3b9fbc66feaab7a64ea68afff84a661015a40c982108d05f4de40bf230ef81dc2fd12a6c63c7d48b1dc683d835c917e5831ce5ea03886314b98042ff8913f0dce3e26e280967226b775d2a30bc1ab65dfa9f1047c14cee4d0306c56df8569a9bff0f204f548e358b208f1dd7858a92821628225a850ae20b16f8ca779a2541585db427063e60a01159099203fc898267f07dad4cf5b83f9aa04368053b3e8819da2c5859fbc601bcc7759c83f4d54f9bf1d8500d092c808d20c82e261d9c1f79303b0f350a8c4a3327f248c87a97cd8263aee3f9fa59141e9fdd97499f338d37170d6e68fe0d0d444f7fd6a164772e70708316fcadf79cbcc3304f1ad14ec3080e16dfbf3b4ed8b9c9c9d2a09147d185b3374c1403879096791ad6aeb489c82fe0b7faa5224cc5e89028b9c862f42207113b331777bcb43971dcb2d755af731dd122770cb3a570d1865a52904abb523ddceae5edbf531626756292606c681d4bab55dbb3e001756b7092dde95c044492df4d3ac5f80b9b3e32d36881ba0d7f50e211725f352424d78c951d82288bff02f924466de8927b24879da198c426689473a7128c9b085c8503b1e57be90e4287a9ea9a5c3650f5bdfa914ede9a34d1135696d98f2216c3a92bede8f5b8f269d12121b5a734e5159c8294f506e2acf2094527790c248aab137feec69edf235fb58de86ddb64b1c650e16f5388d67daeda7050482f1687b8e869ed1b18781851d1a2469a097d042515776529a5825ce321333137169f85deb90a1a272e435fcc314671fc372acebf0f2bc37057e2698ed0d85fc5c95db01fa5d7eb9a7076572f408c5427e98a5821e3235fe5ba8f442d10d0c85b3b2bd1d82d2b12328e9f142535211cd1d65edcba05aee17c43e47fbc6c140d84f5bd0c0af1beb073d7e8eb33065c7b357b9847e0178f7839b6f269b062cfd6dd2da52852d10e00fb968ec4e1e77b551dde9b0fe0bc99c02cd05475a2b34cf60b17ea722216e6727e1850494ab143cf672475d4c773269d62fc7c924699de06a369de48672e41adee91f523ad0c7e1097f4730f90faf542e315033eec6bf0f35841de85742296f23b17a03d75ff1f9fb2c112e3c8d301774192a52f6c11c1ba3670c25afe8780f8a780196d9e9390002148277e2e4b2caf58924083c6ec6ea31211d7d720b75858d547a0fd0e1e041ee35d939da38c76f82b917f12a05166ef92b927aefd64999d5acc71ab26bd582b27fdfa24af1a3ca81a0f103130164bd2fa53ae3e765561d24b7ed10e0689bef5c46df1cadeecd306e8215b2839fa969c5c20366be14e5ed5982311a0be752fe92f942fb01feca2783a0728451fd7f01697bf29e68f01c6c212919be9cab7a9b916da16ab4089c1ef9801a8cd1593799ed97cb3bead9b4d01fdcecb7cb9976c43e6b6a4eadbf61388ee4fbba0450d5c1727d989febe62242ad88ffa7ca0831e1cd7bf1f86c6ec013a63dde5765b462c081603328d2df29819fdbb0dfc0053761259aa44f3d1f03199cd1d2102616d1f5926059190edc657f04c211fc0de0fd65aadbe7c7e928cd4308b7b399acc289851d6c9bd24910877f096483582a4ab6baec0f9c3e9052edba3b493b74308faa8edf36ed0884b0819831d3a6da52f6f0671a7b9b12a055628dbe6212ec3ae4d42e6d89ac54fab55d5d2d9728fe98621778294bf1deddcadc376f083753250d0ec71e968cea09de0553810e927b1b41cd07df012bc1efe76182fbf58da4a5e19da4cdc1d6c36b10f0b3e8471397e51515663d693f96a5c8d3dd0e7a7d4ce41f1e6b0ef4ef2e1cabcae61007355a4a2e66be214f6b34ddaa2bb7a0f09ce48917ff8b600f1a8e87ebb63674fcb13cad349dbb2668a05245598c842eec2de51574639b2fe20cddc032e3a1b384c1aabcc5a93dd5623ab9630343c078f1d0f0a31997f28b312c6950fbf65895291a5606c5fcf21c7aa65e1282d44e073a2645b980ccfd084f6c4f65b3cb254b6ea6a09520f32eed0efcf450da972f15a4ed059ccc9262fbe648e9860d087d09c51fc4e7c7b88a313b30be2f1c84ea2a7c20a3058c86797958e5d9c6bff28ae88dc8a544487864b9c6bb3fec4262a9872c1f4fcf3a88a3c9031cc2a15292f9fcade7b5cb4810911590cf76e13a7a60ef4cb3eab3b5be8ce8e2b2e2867fce393f0faa1a5031916e6836bc0299d7b0752ae6af6f2320ecbfdefa3fc0cf3294b86aba5663ddac2beca6a4bb62db4df7e1f21cdea3b5cd532784b2be97ac30376528ac3abcfce7f0292f063543aad0d409439103a4cb03067a0ca8a3bc371708e45840864619c0f286b51ade042561d3b95efed4bb581d32e5ec85350dcd463977fcea977334f38ee85376c76a18bcb5f088082fe147a7c1764582ec7ed4bc19a3c77c24bcdc0f6cd8eeef9b141cc43328b8ca7fb0df4ce084052364ab09b01fed11f099ed6dc5b159b36ccfd756b54b7fe52aeb2cab35dd5b1a96f40ba6d11d1577f22dcf5c8a11644334a33340b80e5259fc2a42d6860ee4db9aa36e7b571006b5c2e9adaa044724a4b16a696918ce55cdf957b16baadca8feee71f82536b7e6cfd1c1cb1257db612e87c86a862500dea69fe3b8c0ddf1fe25c43094e3e73a0adb9826c27d0bd011cd51f3eef90ae7c127c1009042ea93791d2025e9e31a4c7e1522cd8017b050f6d6dcb64007346bbbb6ef067d22480e02240f36af31e2e389e85674463b586b7d75f249cb72012a5a3967c9d50dd9691c6ccca9ac7b553e33015eaa824d76ef7a3dcd6a183c8193941ae9f09dd0f040174b686a97229b959c3fce4074ca26e66bc2c68bc350c3ccfa6156f786093b0e1781175a0923e290d03b266a9be78adaf0868c99355cdca2413eb21db91f5c7ee6698961a43a9eb4a8d82205f65612564908e7a13d7d030e3f302d4d9fe70ef8aac48f01f17a98f8915c1157a435674371b04fb1219ed4e17ad9cd567c83e098ff9ec4cc356ab16ea1d33f73f0b583b130ced4fbc49eceb68131a67a37f0cb2a053c32413deffde92f253bbc4475f2af218001476abf87085332660bad14ca419753efb0f2014055da54dfd94033941bf6e2cf0e740202143693a188e4a1563c5294656e80d3c03f10e63e2a155e004ee918db41a1c6f9c3edcc86a95f68b99fb60807d93280311cc47baadc992b73fb56c4b74d762be4e81ba0b9813388ed3af4c2fab4f66be5ad24e0a3fd0888fb23a5dc08967bf47b3adfdf15b262696a5ba2f50486007e9a62a6fee1667a2d362d8ba73cebf82f255c3dc3c7f42164babfa31e5adc957b7cfef35d90ab018c55fa6d40321b4447b329568d3745b09884145226aa54fb97b968a41ffdbabf544f6fe43917caf8907767114b90ece2f0935822b8b435067530905e7feb6a86679543ae43b0cdb27bbb51a339629e0eab8f858b7ae70a1bfb83567cebde3c7a1d1ad80e1328bf2b032df80d256a4b636ffd89c64f27d492b27c1a8597876747874131a60b30ce82135ab371760d623003ee0ae6e7a8f688b08ea7d471946f6024bba0513d69aed496a335f702d1b10527a37335b9c4fe2dbb1ae514a15b1d12f696d9dee9559321069ecdf607758c3308e393a75a7b5e509ce4cfc6aaace997ed3fbab109fd8a41b4ef8d15a0eaaa0d1a85a93e5098fedeb4bee8b3fb2ce3a4385143368908c9ebb689c74f6e40c8202f8cbb173727ae2d0dbab412aaf776984f3cfb1ecb04b1710bc125270bc783e00c62801f990623123ee1f693deed4d977bb6e6dbf24bcca6a4aafb28ce225895885638ebb5880295e6deeda410993596a49229e55e625a04ce0fe5e57e9dcbd5f3a167afeb447fa462196005a03ff9ccabbad2a27b99448e6abec50efc0c848806656778c1d81088bc1a5ca25e373e7d15f62b0094cab88be26abdac45283d5ae0ee730f983dce186faddef4ddd3c4fdfac8733076bbbdb02ae0980e1a0498d14fcc20d21f19c5ee46fb19af8791c1785b9c58e55cd067da04828200f9037ca9524494df7711b763c613d00cc17cca533276fdfa27a5a445b327c619d8adec8a7ff30ebd8b143fa9c6e672c6980e9f50897c83b6c62d89f2de753e6782c3182e4757cb4c10be3fc1478d77d7247afd87d32e465de21e02a0c863b5096d326e28274bdb98bd52ddd3027397336b5d457b335575ceb51b5abd622b4eaf6510cc33eb0e4ccc51155d9fe5fc8be9b9ba94e540b3d08f4a01f8429a8245030f7c9e84ecc30807f12c205b3b4993acde89a2973a18d809cf05d0b402d93d4135ffee7f36931028920603b46640799b7b74da3c0d683bc86c457ec1c58b1b07f2ddb58030ce19040504c702ee98c5260e3860f26439ff00000000000000000000654d5b34f50ab0b1943225cd220881d0e1bd29a594924c91ec0ede05ce4c7c3a33f1e96a3c4a2c080f270f5a0d020e7534615eb37039ea4f52fa67c224ed2788fbad0feac784b13b4ab49074fc56fd12c6891275f5444e50dd12e692dbf1838cb822f34a18f2251929f2a3236d4a98f2c3425790a036ed4918d5a34576cb3969d6923099dee8127512bbe64818fde4696b49f6a331248ca62fc748a1d6230c9f1744e46c2a878896230c2abb7ae2585f97ae1a61ee6e910fa16384d1939ab6f58927f6220cd9a3ff586fe91015610a7695434e32520829893075070b615c27ce081186a452c4513998e8ac1dc2a45377ae5421a708933284b145bf9c78b59a3415c2a0ef4ea558b952ce0e210c7a5566fdaf4389fc200c4a44ff52249a12ba200c5a4bf9b925f1e207c2743226d1b6c498dc80302969b254b89427e73f984c45f1fd8af325f583d12abfcc868d67d507f3a86d7bce913f12e683694e2b85e439e5cdf760908b60497afaa473aa07831893e4f1dc5427e5c174d622ea4124d3111e4cdada29d7f49208dfc1b89efd56f6f48532ed60f0a4e3fc4a8abd15eb60d6f299e855f71fa28341a91def30f7bf6f0e2695f4d6975e6db69383f92587ce29c89b2cc7c194fdd242e74e2694050ea6bb90cf295ce7f8bfc1d4a523e3163ca5d0c80c3798e3c78995fa90272164461bccea6f327fdfe4a645d960c81a4a7e5c7c93bd1129cc5883494652f6b33c9e792535983a9a7f124ad6ae677b238c1b27601b5a3c20c78d306e70c0c3a3a303757434c0836b1233d2604cfd8bff8e64b1661366a0c19cdb3e4f27c93f4bf3780933ce60ae0aeeb2a62e4c5b879330c30c86646a1ec3d4459dcf1f5ac41033ca603c53972e9fbda5faf0432b8c3ac40c321854ac1461e9c7e4b8b888196330a911effe19b25cc7fdd0ca220653f253176d2e29dfc6881961309abcbb042d79fa24a53870e38bfe020c0f0fdd46cc008331c2e8e0978329fd937c68554707fa8e1c1d1d1d1d0cb071810cd8b071010ad8b0c1800a2860031d1d0cd082023620d0d1d1911ce0e2d1e9e8e0c3ca620b8f02a461c6178c2745e7928abf9fb57d682130beb0b2a1c5036cbc6086170c294e8a2abf30a324fca1552c2b05a6011d1d4a98d105734e42de7d5cbd8965b1a1c5033a3ac228010e1c1f555c30cc7b597757c7d9896fc19432aa934ed2bfa4c24a84195a30fd65865c9a1c791e643f9891854208117752047debdcf0a85211ccc082f12a3d92fb678410641f5a63b88799171be8e83835e30a861ba194c448f22cb3fc50c485f766b185870a6658c1dc1f79462fa72dbd592b077a40154c15765290a4ef1552c83431830a7d124951dd5f6494b2ec0f33a66012f1645c979e2ed1b996a162861408c974945341ffc4bb44c1284a759b48ea818239c2fee77cbafb21593eb47830e309e6cf2949faa01a42ce81485022e0811e00c60c6638c15cdd7144a569c49d3708339a609aed4a1247ce844880b8b8f145175f3a3a3e8c1b5d7cd105032eb0d786194c30a5d23b734a7ebed9e7432b0c9403c7093c8c0136120d021d1d3618108195c38c25183ff42b4e69e9432b0c75c20c2598a2499c1366d9b2cb640a3392600c955e7ef4a8d1e1181ce8e8f00275d1d14182f6a22b60596ce131430a339060709bf86eefd15446ce114c793d2e67427ed85d8d60aa8a1fb2969785a09345305e10ed6427f71961238261e48590e4268908bb8660cabe61be32ea3f49e4430b35d0012fc4c001060aa3a3638610ccfa796478875356391604f3554ffeb4a1bf3e54528063011d1d07a50087d702c18b35a947cf2549118c1c5ffc00a5733f07c92d3adcc50517edd1c1a198e103820ada9216cfa366c1713a3a3a3ac648010e1b5a50c04647c792320314337a507ac5dac57fb4a3c386160fb0613a3a520b8c1c5fc8e0c40c1e28298ff8d292efe42130c408030717efb1e6714e931c371a0c2de63063078553764a99fd24f1b40e30c97a9767d23af8978335e2c91161e7fad0b271540c1b5ddcf808a3e304365cc5b0412e8c1938484394ba7db9acefc4e862738851012f1c801898710353c75291277afaa6c77474a0d3d1810e18288c1936b83cb4b2a27555981355519f2e28d5eeeb9ef6c08c1a989332935632a22dc73430582e7db2e3d9a8d8a519184352ca9f98beb91cf4a1753c48e0c58d30c428f580193230ff491e5315235abe130343ba14eb262e4d7a555098010353a73c714a7b04a5447c85315f444b0c952913e40d485798435a4eeb71ca53d0ea09325a61b0fff82113ecf652e443b909325861ec10745e0a7ae1828b66c07a94d92a4cc952ca3125864e7f990438b8d81b5fb4475145724d448e9ffb4389b848980aed448f0e22f447793fb44880838b820a446eae35be63e9280e0a2419a748bb2a5b4a196231b92c57a8e457da52ea1082674940021c14f80247161d1d24c0c1051630727c21c3146631ebd92f7d66b22e85b1840a77a334b7cc3b1fea005218df72c7fbe93c7fd2330aa37bc8b51a222b3ae488c2b8ba39737f79b94da13048ebab529bcb78394151d0ecaa2473b19242085e31dbc3e5eeb258fa274ce74937ccc6b5e43cfc810c4f18d44f2e6ead43041175c2907a7c4d9e6e479085208313a6f750c9498fbcbc71a1206313a62de55133fee37a84a066b185c7870c4d9823da8585d44e9194ad870f6464c2246f7fd5b379bc104f1f5ab800c684495990cf176a1f529c3fb43ccc2e61dc5e177133712b21e9436badd22ca1e40cef4a33eb50f74829f4c55289be53a9eae8e840818c4a98746c390f63937a3e871206d5f755ba3f4499a74cc260e97482eea89c728eebcad0810c4998abb7bd6ef2a714ceeee8508fb2ca620b8f316444c2d429e8afecb752fa7490305e5985f533cbdfc98e8e29c8788469af4a8ff567952895644718747678ce4194f8200b838b2fbab8818d30280f32c9f72ddfe49911a652426a7d12312148cb22cca15e2746ba77dd0b51451884ab988d5c1f9b381f305a8c07c04046226a95afcc7675d716936c22eb59758a5d673afad0422428e3a24ba9173210618a53351dc3cd22a5500792710893b08a27469f56b70b3284498b87b414d3cbfbef43cb2329a080055880020fc81c29f0c82a84d1248ac41429a264ff0fad14a00fe3aa2cb6f0e890410883ecc812b267ee63591d1d1da43ceac3b8d1c5657641c6204cdabb67c4c7ab569871242f22b04590210883b65136f134cbed726c041981306ae90aebb13b49bf7e68e1485e8431465fa0a3a3a3034722eb515b00613aaf889d54fd5296fd07b375e414e53a7b49c9fac1a04d2cf4dfae75baa40fa63d7d17a37129dee9f9604a9753e50575899dca3d18c4eecc071d7408a9a21eb82c16da1e2221b7569ac5a52ba14cf6a710a294910783255715f390f1ee9a6941061e8cfe21eba59b0e22a71cc43b982fc6470997920e7d273b185c268f2419b3a2a746c43a18740ecb0fbf8fe3c1a283495fce2a9fe52c5b8c490f64ccc13c93f2a91331ba15ce8e0e948329e7e6ae4775887130c5eb8f3eeb26d9c2080743940f296a2bd15f2be618c87883c982474b3a699d68c974707ce44015b8f18502b04e50e5515827286090e1068304753517768476091e14c38616361cc5b081d90663ad075313f16be3b35ed41064b0c1f4af61a782a80a42e7c58d0ff62822c8588331ceb7c66286f7aee9432b130132d46048712ee44f2de5f7f8176368c11ecb04196930ed4b0ab65b412c3fde11464a41061a4c55356f29eaf55254f4c5185a78d418398c2c41c6190cd927a7e7fc53bbd2e9203c906106f3e76c974d854e4b7e194caa73a4f8254b16e616120519643098968884d92cf131fcd02a9cc5161e326420630ca60e1d7a25ae3afd28af38b06230fc8a1c0d5d93cbd46130c9dba5f1e8e597c7d2d1b1596ce171021960306a4a0af6a5b6e5f4985e90f10593a49c60bf9de5e2c4c600238717a6a3e3ea041d1d57e5515727285e30c9de9ca03b6f4770bfa3a3a3638c1c1d1da6a3a3a3034979948e91c3ac20a30bc64e2afdaa7d1811b6e5826956823ebd65ea3fbb2f7080d1808e8e2f708071148023871711f0c091c38b1bb9596ce19143c616cc419597d6bb8c4eb9ffd0ca916e7c84e181a60583854a256eda2ec4087ea807220118a80b5239c0c011c6c9c882294a48c2d44d69a81e0ba655fbb9dffc5092423eb40e173822d060a01c371c608301362260430b0ad8f8a22bd0d1f14577d1d111467774808172dc30525730b987dc3e21675cb7e543cb0bc485070e2f3c3cb8d0638c1c37c0e82fb6b2d8c2c30132ac6058bd1f8f5fcd0ba12f0117623873d15e582035a200036c70c006036c4cc006036c1ce4050a2cd0d101062a0118dd454219400747161d1d1d1d1d5a89617881a3ca1432aa60f61221ba74d2d6f2aa0c2adc1e1b6ed12b2e8a569ca9724bb2824e42459004644cc1204412a973a9f8bc7e7f68d9d0e201360eeae8b0a105056cd455166448c1287779e49f6adfd125ae818c2818b5824ae27d54585a12148c91cbdcbff3c4323d7a8239c8f8b01d7664c57a27986d2e7bc841549e4e7613cce9fcb2cf465cf0ab9960d6b2385e16c44b30c9ced97bb13333db2ac19072c96ecc276df92b1f5a6c438b0744000c9423025de000c3018fbed040474747c7a3132c290f195c90910483f00b29b8aea49c8279818e0ec485777478b0086420c19ca2a8cefb6b27ab2f1b5a3cc0460a4e0448e04567a0a3a3a3238c2f709466b185c7917104b38ed9d7a64e0c912f1f5a1e6370714630e4882592d0c9c272524a194530c552ca2bf29d926aff87568e2f7078708911c60d30488014904104d5bd246904d7b2285ba23e42b66da990d97b81c3830b2efa19bd1716f0c201b9617c81a36440c6108ca1ce4627a5105e3e0819204308e68866d9aa7eb46b4a2c01328260d293e2de5c50d1dd69353280608ea6638724ddd475f278816e01327e604cfbb8b5e3bb3b29cb0f90e103a345c911e653723ebd405c7818620364f4c0a0c352486db1d425d73c3027cdcb0fb370b1941d984f07ef115b41d427450786782a49adc8fa51ba529903f3e588eaadca29580471e19d438c2e4891810393aee8f13b88cfeab33eb43c3cccb8e002fdddc0a8997121f765e8eca90299c5161e21906103f3092139857998ce764707175ca02799c5161e1f905103834efb21fbe714d513fad0f208e386950164d020d30cb1942d468c51d14aae93baa487d63b95225740c60c8ca212c44f82278d94f360193230575c0857f954475cdce89405468e304c460c4c1e933c040d25f3d5fbd04201f2a280813400460e0d54291839c2900103538c94b2596a16c35f60fc6cbae6632aab5bbbe1027374ffaa3c415d50ab5b6981f9fdd3757e34d32c912c7316182c9df0a423c8aba8fe62af606d95d5788b9ea55a2a1ab6173d787cd091c50fad4b313c727491f48b2e6e141518c2259dcf4119dbb90891f208df05900673a5a04576129159bd0b000dc66e196d4205a5afbb0be00ca6eeab10c278ea92b80bc00c661b9537824a9ebcb70ba00cc62bf59e92e4d5c8ed02208341f75adc6e7879785d00633088777b9220cd3f5d1780188c252aded39e3217d5051006a3e4beb36baf155dba00c060defe512b71724b0eba00bea0863449a594e85e304ab49be8612efa82de0543ba5bcf614ba80a9f73c164a1163241eb74b4f8168c7db12f8e8c0ea564ae05f39e4a3ebe9d2b3de55930fc9decc785efbc9563c174256a24d6e79cc2c4af6092ddfb0fa74a66246e05d3ce4e2efb4beb29e255306c7e85743172b809712a98440ae1423c53597fea2918efe25e4c6895d4a29682398b5fce139383ada8a3601015a162b5fce5f734144cf1bb1dcb64d77fa79f60902d265294ac9aebdb0906596d2d7eb14aeade4d3089acfe798e9472d49b09c6bfb259111fbcfeee25983d7546875813ceea568249df6b65cb25c1fca52c66e5fc25374682e94478166bad514a4730a91c9f6839da997a8d60b04f3b718ba5d517c1dc41e2b8de754d9208266f112bd9f72366390483b2abb0d9499b654230dfc99568f3f4e10a82f923c95d33b79c900382313efca67d3611453f30a8e9f1a44da772d17c60187927777d3af6a707a6149be79aaaa25d1e184e8b48426d55c4523b30b56796d01dc4469ab40ecc71524ce74bd1a3cf8179ffbc544ef1b3741c98736cfbbe7b7394e90626f93d112e4fa5f06d60f4dc536143671f6d0dcc63da22a9143f217868608a203d789a4b4a3c333065114b7eb5af63791998e4780e6944f2c489c7c09c6e7bc3cd4f95580030308bbe5c12df8386aa5f610cf1e3ee21a416df15e65275a2ced289b6b815a6a0a342cec91741cd0a93c808792727e45d7715a6eca2533c347452525598536a4f22498e33752a4c31fea6b7947c191315861915314af5e9bb4f61f888bf6a22929c104d61ac64aac2de2769bf14068d1122f85790778f14a64c09b9434aa6b43d0a430cdf70d3a373cda230eee5864acbe2490d852186d80821cbe8a50a288c1641dc7a1abd58fa84219c9035f97946ace60973246159164fe4f6d50983bca52f9d7c43c40953a898f57b2194be09c369a6d75babbf49581306cb1017649b92aba54c9823859c253febc518136673dd8916637127ba84b95355eed239a5b3254c7925ba4e6ad35225cc9d841e2d711692a484398c92f21e391b804998d3a91c938f7be1f90d80244cf926a8b8f88e277f0310099357cab13d05153bf70d0024cc372ae8a4762fa8ac6f001e6112f62b9eac3c54e91b004718745d3c25a7c747de1b8046987fccd5353c8a4ef7068011e58b8b6b692fc22817a26bec3efbd6ab0893be99f0a94c9918f126c27421c4c9f9bc0e222f220c22dfd5aa470fd6f11ec214ec6368d3191725bc86302891fd6c17b53b69b710a690d95a93b74d86760961d44ec9b444b2ea78771086a482aa143fe75aab2b08837e481e3a5ee7a5ba8130898d5ed3724fa6930b08833ae12924f9d7f172ff60be9053dae7f73c92eb0793b9955f7f6cc927dc3e18b4c5a760dadb4387cb07d3e95bd4889d214ab77b308a5296e30553a5fc5b3d1874fc52fead252b95cd83e1545cdcfa0f22766af1603251bfb174cfdbd7dec19cc3dfa9f8d2dac130b6a243d486e87caa83c9cd83ce7e297e94101d0c7e26d45e65bb90477330dd8ea7a79098e122391844f2ab7852429588e2609225adfbfe729220828359ced32995438bf2d01bcce61155d4ce64a5901b4c4296e696cef2a374da6092f241d77b6ff9e7d960f8305fd32d59d4f73598fc3eea8a88eee1693598549c7a8e6fa3bbef3418bf4b928b8e49a56e34186247bfcfa33baecc67308f473a3735717f643398dfe468b87fac895b06f37672d1395c825d960ca68adadd7271a2a83a06936e67579d0e9f51c5600816e44de854c36018d339e7ca675d972a184cd983f8143d5dc84eea178cf2d142f0bb51614cf58229fce8dfad244d9c50bb6088113545e4f5c9252a178c9e4c97757874133add827174e9fa522972eb532d98e487f6f1b4d9df9e66c1ec79de65c249bdf3140be6d32b26db74928a9d5ec15452dee497971ed5a9150c6af672e57c234e2b5a0573ffb85a8b68a59129154c5f214ef96a85d1974ec16c39f2d2e4a552304c0c33ad1446be5e1a0573189df4f7ed49bf9442c1f41f534d4c321dabd22718df538e7c32fa3452ea045310abb61d7e233ca909c69914efdb4c725f8909a61cd3d5b5f2c4dbd1124c29f664dda9d68a2225184d7ce5a8b16942889260bcfc9f3e4548f96308098618b92e8e6696d039473009ed79458a7ef1fb18c11ccb54d8cf325a625e0453e71dd12d924b049908468b95bcc3ded3321e826123b8b985bc5cd30ac124f24f4308b5f9d206c15ca9a25dec1273f902c1a4f48d7035519e63fb0343d612d9d80e9693f58129e475faf11cedd2d903b3241da66c92de559307a60f93b5bd72777a7107c6ec1129f9e59ceb431d98523e35a72dae2e04736056b98fb6271c984a08bd54156ebebc1b982ee6b5883c422495b381e1dce4cb662bbda506e6fd90be734a13442b343048f4f02b216506866bb3f477a6727f1998f6f2a5be7c1680189827fcaaea7bb893900c000c8cb39f3f0415cc279f5e613833a91d8229a147e40a43ccf41bdf1ea5b2a7152675d24f07fde6dd3e2b4ca1a4ce8458dd29c857618efc71210851263aae0aa3c95e24f1bb92c29e0aa3ac9f95dae9124a4785b14d74c6d73a3bd2a730a910420a29a25be86c0a53990e1f9b6817342e85c12ecee598a43d3a4b0a43b2ec30e33fdacf1d85f947a791aa16e28c5414862092c6f25e5ed069288cdf6f5a74d46c352528ccd143fb6e890b62cb4f9876a25dd6a89b96113d618aac9fc65ffb3a8bd809c35bf7a72795f2b8879c30aa569cf9a4e3c356701346efdfd51bcf58db5013c6af88e31772f6aa61260c2344645d3fcd101562c260a174ce59744ed5165ec214ded52687279122859630c5bf7e91773f5e225809a37b899820f6a4de042961588995ca738f9220c149183d09b99c28c13a465012862492f697f33d6d53240cb75915c463e72e11240ce7594eb678d4b1ce234caa9df2ac750851721c61ba78294c899432d2dd0873f0a06325ad8c906c46986a45c743fcaed17a11a6ca613f7faa1561c8a2d52f5d26050f27c2a05521be84904ee88c0843fc4f6963525eccf910c6cbfbb09e447873d91006b73625ebd92d4bb810664f29be2e47937a2f214c154de4902b6bc7640761ee0e17d79e2b458f0ac224e22b3443c949bd06c2707a591b41e4cd5a01610a17b75752e5506bfec1dc3a73319eee7b4c3f98ee3b892829f6c154f952523a272547857c30c67b4c512f764a04f76016e52917b7b4dda9e8c154379e3b9a7bd04fc983e9b3c7cf342b95c48207b35c8a5ef152ee0e86242271249b851cb6b38341fff2478dbd27a1ba3a98f3e8a4b4c9bffc5d4707b3bf053d1354ec9debe660ce915e6bbf84acb34e0ea624c4c5d0665d1c4c5f393b29df99d1d5c1c12c2acd3afba4d021b93718747d8e16d265b9859c1b4c21a47cc92fc4789e4a1b4cca429b8a27bbc24c850dc63b9719a1274ab6a4b206d39608494c7f890a49450d261d29f4f66cce7a50498369b77e44079584d851418341d5b729713a67309fc8176cddd358d09bc17c3966c514b730bfcb60f2526ac9e45476b8c9609e18a392893a25293d06534af899937b663a158339c44b8ceda04444340c663fd5652a476e3d25184cf245fb3cc8510fca2f98824e4f7d923bf29d5e30ec5d55b86855a5845d30ad28295bc1db5f3d2e187ee47d2d699116de164c3a3492e494edde2d450b26957695528a234e574a16cc9e21fc4feed3aa56b06034d92b3a43cfd7b47205531ea1e39d799f4aab58c1582179552fdde81c52aa60189f9b371d299a9a142a98abc53bc799a41249998269255e8aac8fca21a448c12c9fc25f9c9a10dc140563dd97eef211b12f41c19cdb62d6c8899a557a82314c0459dff0207ee404c327cb9d73f8fcf751134ce2ff3a9a0eadb6212698447bbe3d49119d9f259882f058f99f2ac158a35ed2ccb597789260fa902f879c171adf418241e54b5335ab97d43982c1276c75988c07fd8d60feec1ca15f6257f08b600afdb6bd6efff93d110c7b22a88890d30ff92118dd5e823e89f5317621988294305ada20987ed465edbbbb5b81604849f69cd9769e89ffc07c7f633a9afacef9f681f9b3e44f483d25443d30b6a4514a5ea59c9b07c61dbd904f6df55a76608caf149694f24915ebc0203afa749f889ea31c182c76dc4a32549e6b7160d0e1e63cdb8627a5bc8151c6d4f7e27c5d6c605cef78b97204400d0c6af5a445d2a3cb4403000d8c23ce4d88e6b904d1003003f359ec9024e2e2426700908149e32e2bc9d652f262008881a94df6d228ed9f74c50000034358cbc99fa9a7ab62af3047dc132be255e59bb9c22056f9e2267d236fd60a83f8a04c243d32179bb1c264e371a7bb356f9aadc2a04364b7e4a74b3d335598c3d78b855d52afcc5261dad2318249b1adb70c150675c1f2dde4a3ad65a730b9cdd9e6b756c8ca4c61fa8fd79fe394087262a5304cea0f228418b99718294c3b4954fe733fe1111b8559fdb7e268b54f761285793bb4dd4fa130a9c939f7ec75e712280c3a4794cfa9ef92843e619c74958287f9af1579c2ec2772bef956d025ea8449891353da84c7d10f270caaab73909fa329cbd984497ccca55c95db0f8f4e6c13a6be4b5a748eb524dcd584d9adacbfcd635b9c18e74023134657db4f1544ddc28a3061d23542c5d74ed9e9534870238c93038d4b98d35f444fd1beb24a360e342ca1031a9530482cb5bfee55d1020d4a9843de33117a347bfc340953eedd5d0da1db69b637d09084c1f7a4a84e3169d6741ca7dc402312a6b2f09642f88b2aaddb60808d2a8fc2bf41bc40031266cba52ae54a93f408d1a3d60f341e610e1e92abd3e457d4acf2286b030d47982b77869cedbde8cf3c8a941d6834c268e15267eb206784e94d63e4dd9ef624c145985ffb7444906b95d45784414cdcc8297c7c9d10920843728b22f3e673ae2023a29e8b7a5f253542e31006d911b4d2eeff95951f5a18041a8630eea5747ea1ca2dbd8e0e20d02884b177f544dcca22526a1f5a6c438b0734c0741042145e9a15529c47d0d2933a364947dedf328b2d3c72d0188449f6e8db1122871cd9168479ed743537be45293510e6efbd1961294c106503c2e096c32b68fbada8933f983e4bcbe711292e2fc80f660f2ea64e89ad0f069d1149e96436f262c7075370f9e8af7afa12db3d985a25f55a36d1bd19a1a107433e6176d92c7c1429ca8349cacba9945e912a48f0603221b23d7e8b9af0d31d0cb65b91773ea95c3ed9c1a022585ff8f4a3d592ea602aa124b9ec891c4ce7d0c134f16f828eb9a6427fe660109f4cfda988ecfe5f0ea6d866734a484ea3f7c7c1d83955faaa7c0411f27030bc68ef503ae427f5f41b8c659647ac6551a67fbbc16456793ba7d00fcf761b724f112476459a0d26e5f6794c97c6ea87d7602ab12a77515d6c7b568321e7f0b21d6ae9737cd360d048e5312567c434291acce53b3f175723ceaf67305b644b3146c7f512a519cc2632f3f5135d479e6530b9454a9faf440673274bd115fec13a88633079e44f954fa7a46c1683c15310978d71ad515e18cc7ec2445a8b106c9260306728b14cd3ef9d625f3027f99084677ff6b58a170c72e9e6d6a2b3eea90b668922d4b89dfe9f665c30991699cbf399b9a02d1835ae44eacfe5a4a2a505836e8f6c6b2af5443a0b66fdebaa9b8f3d63914727408706162ecf13fb27c70434ae6098378b6ee923c7f70034ac603a93a7d921b625c97b0b1a5530b8a81051f9ff65b18587046850c1de929f4e5cc9694cc110c3daf5446a49dfef43ab541611f0c20268071a5230b7c773f7e8b96358088d2818af6ffc45feccfb50305ab46a3761cadaa345e30926c9fd7931d1e752d08b86138c55fe9f42dcbcfa373fb48c46134c1d17eb65440ad12cf8a175349860cea31d6fc288f4bb7481c30b8f62349660d8d069e9dd4d82766540430906f9d993369558d2fe940463e8524267ffeb432b6920c1d4913afafee8f049d90fad17d038827194d65e84cb0ff5bdc041ce08861817bb449cb39e101a4530980a217c24a1c29c443fb49806114c57493fa4ec6971c2f7a1554a690cc17c7eb23ee8ffb00ab185020d21985e42b624438d6f8e290846d113235876db6b0f73126800c1983f716c467b548a112381c60fcceb36aeda254727ac3e30869e38396a699bcfb107e6d2925afd633243728e060f0c579f6392da1d982e244f349b0f1b42a803838d77893ca7b2028d1c98b2599a0cd93629fac141758841e306a6ac4fba935dbcd8c0d4fa17475eb890824ef198028d1a982d8ffabc250fcba212068680060d4c67b5a274aa8d12ad7e688180c60c0c3a22cffae6a494cd420dd09081292ce4d25f3515460da3f3ea013462602ef713a55773427e0b060e0fa4460306662fffecdeddbbedda87561837b05e6198b0fe1fb2e8b34bf5c7e3f802a12b4c418fb6c92958aae9bb15862bf5c14fb542cf231e3950188515a6feb89f214f6f5c8487c52acc37e1520731391f6e548579bb8338257e438ac6022c5261da0fadd6a1f388b5a8305b249bd4653f314bfdd0ca53987fc73e2fef238b8e9d1b6298c218bf32f2945d98a4d687969696c2f031e2b8669a12932485b1f4f55e44549b34ef438b4761524246de9494b77a826e88c2a052c558195d937310fad0f2900116a13048d6aa24423835db150c148618a03028cbfd6cd39694ddf3a1f509831269679692a424be89910379e1099310d66127de8f9630f9d0ca1660d1894524983453fde571c2a0425abb957cf1bec381030c0f9403c7093c701cab1c387080716360b10963e7bcea8634359935618ae5a322daa7904ae83fb44ea94ce01de2c99d55f8ae90c08b1b63b8472915062c30614ae9c26cd6466c7601169730c71f2533d652b4548696b833bf73f29bdc8b7e9671042c2a614a37f5b9d4f7b342c88796078e63c6032c28610a42eb5aa89b5cc93a2c26612ab9924d5aec0a5b9524ccd7962ea2d6896cbafdd02a2ac0221246f31fede12b7cbd993eb4ae568005244c671652249fb3190bfad0f2c0912307583cc21cc3bdac647bfa30ff87961728c0c135032c1c71c78d302ad8e906698469afde2ba774a1cd4e1f5a5936c08211263d2186b430dba20f2307425d942dc22c29cbd552a99d6cfb87162ac290da23567ae9241fd68796870e018b4498523caca50adb41640c2fdac30b1c1e0779788471034b18e7861807042c1061b6acbe9e7455d657fb43cb03c7b94398c6f284f5f6fcb5a1c691c3e3d109ca0c6116cb9222a8cd0a61aa7b773f15f6123d240c42984de510d53ffb1f5a288306580cc22c61ccc26cc4f5b72905919674df974dd55f685c3017b008843144895437f3094a4b608005200e792de4ceabae65df87d6d515b0f88329c552af16b5dc8c8fda0fb0f003712d779c8be039e4b15ac0a20f0695ef55a3929a4f37fad0321f60c1079396c715912fe8b46c3fb40edf008b3dd8e91912e525d8b927c0420fa6b54eb751d99110609107f3e8e7893d4fa2aac40f2dad01167830859127575daa3af55d20303c4e70e374813270821b0d40e746073a3a3a3cd64a0ccd620b0f136071075388dda3834f14bf9c5236b4780092cac2c20e269d475fcdc5ad90a05407f347b08ebfa46bb2253a983cabbadf9d6855ffccc1f01f4f2fb6b7a3ed470ee6abfb6c519e5d44280e46d17557372a8d990d076305f14d3b65bfc194fccbc594946d49b61b0ca67bddbb94db06a38f1491c77f3c96e7d860587def4afaabe6936a0d66ddf3ad142f2199aa3f3c0a3580851a0ceaa3e3c7923f69a25d70c1457b385b06b8e042010bd82f609106936d789653f96081866435bbad65b3dbcbadf4892b8b1325676f07589cc1dc7ea284299d77e4431f5a627881632da005166630a967d12cd370f508e2828bc6a20ce6a094181db5637b502283a9d43d4e4dbb9d2e8dc1687336aaff218d2589c1902a4bce9582fd8850188c3f1e4fcfa970ed2030186f4652bc4a6fa6fc2f187654d5979ebc6036191d964f94beae53170c1f2ad7885f947ce1c40553e78d4f8e192957386dc170da947df2fed77e93160caf11a248ba178b6fca82614debcdc3f87e5c13160c49be5a0a228490764d5730ad9f52f71c6ab4cf64059356a77aae68aa75a62a1824e79f8aedcac865a282499eff7ecf9a8709d3144c9193e8a70e269289490a66b79c554b48d24eba140583901b55f12c52f4588282b943d2138c934ea59a20e2fa0939c1a4c3fa44911451a2a809a6b2cd5e8f7871e7c304b377ae17f9de75e95f82d9f2ac9d5d96a0c557823995d945db90b7ba27c1e0bf9bba91ae34732498d4f586a720278b521fc168ba646b4dfc4bb93682d1ae935bd0b2f6242e8249fa27f9b5d2553213c17c713b8585ba4cd00ec1aca2b4c812b3a33e1582498752d750e1e12d0d82d9c4e285ff5c27e3048241a5ecf55db51c2f7f609aa8e795fb830e3afac09cf7633997ea9034f6c0e426a32627f17c2ef2c09c72f474addb81298510954fd352aa4f070651b9cbc6dc947f4a0eccb12a2485d804170b0e0cf2c7f472b89437643730e8ed18b12de852b6d9c03c29b5ffa7560353cacf49f52cf4298d0686ff54e954667c2ed50c4ca94285b58bfdc5856460b070c93996eace98202c62607693946e9daf3e272c6060fc9cc4e9b9a0f42ee915a6ee10725d0ab9c21c232b252576298adf0a93be34e1c4775618ac84bc0ca14f54b85761102288d8b5eef90aabc26477a673286d2a0c96b123d4d7ac9a161526f59df362b19f4b7b0ab369dff3bcd0312e6a0a73fcac8b39b96e394b617e0fa6ffea2d3e43529844bb3f5469c8aa701426fd781db5c37b2e9d288c55691ef6d2fade170ab3fc8fc7c7eb8cb980c2aca6d492aa0e1752ca270c4a95fa3ea1e4df553c61521e3b9efdb4fdb54e183ce4bbdecdb05139614e53173be58797e826cc292ca757e753ea2b4d985ec7f435fcd5baca8449fc571e51d5c184b1d248f7a47b26a5c9b98461b404cb97dbdc5d722c61949c723e4991530973c74b5a136159f3440963c989db2128bde6a64998e5e3fd59f8b2209224617c8b924898946607a52aef723a90309fb220efbda498f63cc2604a757ff4bd16e57184a93a5cdbde865eef34c238a3b3f558ae1c949e1126cf6e2123f2c79a7e1186391f156d2647b7af08536993e67f7b7f914e8479267549cab54e724684e15a5646a8a589163e8451549fd2216e7bbcd810a653c95d1ec45488ec17c2741de165c45eca379f10c60f96520e592bc1cc1f84f1e34bc4125110065529ac890bead30e8449a94e1f2c2e841e10860f41ed477642bcee0fe66417fadd267dc5fd60def96d19b5fbfa953e1843e5f8dcfae9539e0f464bba64e5786f3a650fc69066be1e2fe6c94c0fe61413723271cef33c98a4c88c4e6aedafc58369553dc4ee7015acba83396ee255509deb3f76309e281d82ee60f37b1dcc39a457cf31ae2ca683d9545633737f3b84fc1c8c76954524e7f1fdc8c130fac308f7f19b608a8331c485f95f082a82e0609215c228252217c01b4cf24dc8d38e67fa432e003798665b26e59a49bf1d17401b8c97db22498fa621f42e003698d3d6e524fc74d6fe5d006b30e6fff947c872727d17801a4c391a6430dc7c5dcd6c9d9a34ea17dd4521c018d0392adf939efae82431982e26e3af3d95f0a430e4d97e6d6282f5d5090c9909a5a44dfe90534d5fe0728e7abf242f943e41e70b616f3f5dd82fa68e8828e31e2ee49ab695be44d6b3854524252efbaf85c6e7464f18b36056d32b654abe2de88f05455b27a1e4853115ff2bf84904f191e02142762b5ca25289987815dcac8d3c21c96e0f6a2af89fe20979576927dc533898d949df5c31b1140e7a2b6e761c05827f12f29584ce170a7def69091f26dd7d827541caa854373a05d7094fb2997c395dbf6d02b9adeffce52b5ccb044e7d25d33955c76909241dc7b3944af892d8921cf497584f2601cfda6d298666c93a91e028cdb9b8e511d2d73fe96bbc436884656476b98feafb0b16c1489343555d6ee4d21141ad4fd5adbf6cdfb921904e6edac7bf53f23d21204405b111a226774140db9467ee8914d458809049f11862d4fd8382e4bbacedfa4aa70ff0d86a0d0f4a6d9d3d68b27c69abb6ecb0f180a046a8869a1435db4115bf3e8eaccf95e9c0b160da5494aea41c142d7c3d54b612215438a82f89919fd384929eba81d9e5e46b4ef011a953364847b9f57876b5db550d1069d96249151565d380605144504ad797840033309bb9cb4e88e53217f120800cd2a946a5c75d0b02c4c0acdfab59937a36100006862dd30da13d27939e41a0a3230cfec2005e6148e95b2e2fa490f2a8a5c2005c51ac309611b2c6558be5aeb0a0f6535a458ee4f9a2bb2823f5457751c618402bccf91b9e27e31592526a300056d80056611413f1d876d96c4f4915a6492926498efbd9f6940a83aeb9be94d7c25a3ca1c290b2c5e994bc47989c4e614efae12ac80d2d124ea630291b21976e39cfc4540aa34a9e119120c27d4ba43088ad893342664dab340ad37689e0f9234914e617a5a743bc8950a35018b25d96af1c721825028579626d07952f7da2f309f385699de689af527bc27c29e1db6c2fe48451278c914486bfe8f6470d71c2343a7ffe2ebf9c3dda844165a89c15bbc54aa409d309c929f9d98ec62713c654d12126071346bb3991f2677890f625cc59ff5b54ab6d09531021e718214da77c9e4a984d59cf9538d197e7a18441a8493a294b9f4918454450eac297aafcbc240c23b277142192aaa93f12262526b7aeac2b86f6216174b9bb9ce4297f64ff11e61391f74649df11a6cc7cf73bd97192f21b611acfdef77bf5f1e033c268c945093b4fe731fc4598bd538857c8f2a1ac5784417a360b6a744a96459e08638cf2d04a9ed297de11615af18a1f24c4b110fb210caa4c450561f64963378439574c379d83d4b4d50b61f4f4d7729f544e9a3a218cf77992a910fa204ca3ecb782bfdcc5e78230fee5e9cde74a66390f8441bef3a8eb759f4f714098e249cab95e3d9c67fe07e35cde678db89aa76f3f1894a77829b3723cd7bb0fc68aa7d7d52357d96e3e18fb42f0e816c25cb27b0f2661a3841053a315d9d683d14ba968498df6f067e7a13b2da942ca231eccb2ed226269ef2edd1d0c9aa7365f41ab4bd77630b688a90b1394fc1cf40fc59122503f18401d0cf28378939da38650693a9883d0f732ba3d9e88cec1e4236de498520e06cbbfedd371b2922e0e26b112215ea7f000e060ce5b6d1329b9a45e3c8037780cc00d262197d64ded8e88561bcca23e5b8b981bf921cc06d39fdb89cf63f934b406a388d856fd4b41e5bc1a0cd2431c91af5cf3e36930b98bcaa52a3dfa830653d2b83af5c9fb217c06839eac5b97c499af680643cabe7949b5a2f96530ed9ad2267e72e4cb91c11c2fb9dd6a47c760f2e0298cda5fc5603c7dad7f5dc3603631edbaa257b5b682c1ec419efefc96d2abab5f30f8c4cf9bf914425ed50b2617f75357116ee969170cba745eb1f9e41224ca0553a58f9d0b1f5ee4680be6b04b0f5d3d495ba405c366494ea243cfaa280bc68f6d21ae58a5f51016ecef18a552d6f30ae694a03a76f66d764d2b989356917392f3e7c3ac82b94d275d65e5a8774905434aa7c447fa306f2a3905d387de6857e7d07aa514cc92839b4eb9320a460bfa27554212510f140cf23cab68f720c16c7c82b1cee4b6c725a582149d6050228f38531f2ae48a4d3084d329e80ea7efb22213cc364add82b7ee5d0a2ec1d4f94f8468e215da42259873990e9a6726e727980473796ae74a5ee121478251e28c9daafc8e608e9a6227174ef9db67048308af99122d43a87d4530e6082154eb461e9d3b2218e3836409f26654c86e08e65437226f17da569d104c2e4ad44398536f21170483da9ad5d00f10cc394e54b868b3f6ce0f8c163e7c4a9262ee3a3e30e6c44f21582c71b9ef8121490f094a784aa2e23c308c99d80bff24baf30ecc7ae93a8488b8f36dd181b9f44eb60873600e1fbfe32fefbba538306b6804fb8891f2873730e9b8fd375da2be3e1b984c5608ae7115825a0393888e1f4a58a528d2c014d1720791d76d19293330d9774eccd830ef930c8c292f61278d62600a899223a89c0d0006860d79a24c7d44096a7b85d1df2629b72416246cae30a4fa1233b55a2b8ce6aa9d467d853d95b1c2f0922e053df52d655a85e97f72b0a453ea4949aa305f7f7bfe5ce521e553611a37f5c14204fddc5161d24df5b3b1f0aabc4e61ea94773bcfe78917295398e454f25b0f69694ea5307cdeb628a974486d2285297a2971a64ca330a7b4125248f7d1ea8bc254a792a7ba0c155f0f85514dadecdc4ddcd141f109d3dd054ff9f1d73b3d61c8417a45d21629b64e183fa84af2ace3845152e88997e1e182651362d4d084419f10c2c2a4504acf04392dbe928c4552ebb2b00626cc59df5792dc5437cb02352e61d0b24a352112a4c48f258c2f7216454ef5b7b42aa85189ea3835286158d18fe8d951941e918e0e8f1a933079e8def41809b9ae2309937aeb8f337ad2b8c51f5a5c70d16a36b478800d8f8f1c5fa0a34566b185470a6a44a22e55d736b312ad4bcc620b0f2f6a4022f9f55933fd6f70e1625ca0a3a3a3c30119b061db831a8fa8e108b4a8cc29bba4840995dcd76884e9627bc5e5a4fde29fae0623aaa3c622cc25ee23c8ca1e4d08d7baa8a108c398cef1f4edbfd8af46224cc2cafbe642d259ff972cb6f09801891a8830b788394f7a2b757687c4a1c6218cab67975a4d3ba79a0d61502a584ad96796656d210c933a4daffa65f4086b10a29465e477959026e93408b544388979521e0395c0a3d8ce90410d4118d353086a2772b1b50b84b182dd6de98595fc08204ce2be72f670a23bb6ff8329ab754f68a9aec8329f50c30f063dcba62aa4cc5699fa600ab1aa3893ed38ba3ba1061fcced91634f65fd3ed3eec1a47ef24c4b44b8b5560fe6a82366174e85689dc2851a79306ff869adacffbef98b07c3e7b47a42d7ef4b50d93b985379e56412f46e85931d8ce2937364469abf9db1461d8c3a7eeacbe3a44e51a2834977f6a42d2e5d16997379a0c61c4c9f845e4f23936c6f3998b489e426635ebebc8b832984ee60f9a66647a76e8c1c5f7461bcb0800d06d8385edc98c01860203130608301361860a3a3e31035e0602cf7d0704f327ccb9335de600a22df7d9e3341e72f0435dc6030914597927dd9c39c6ab4c1a0275ece4a43f723870d86d4b1eb73497a841a6b304bf41bdd591e4f7a148d50430de69c63ac76ddf94c9650a1461a0c229830b5b6b510442f230a35d060aea42f87a81ec11a673049cb79695ff1422d7279a86106434a5733912aa93251cb6050f11cc182f04d999f0c8668694d894bf6eb798ec1ec9ea747cb67751a7571038c2faa6c68f180d41a6230c8257b8d1c4df2274b188c33c1746adbbb7e100183d14268fb1c82a7a4317dc1b07797e3e2a9e2eefd41841a5e307d8d054b797e4238a52e1c663c5e3b489f0ba61c347ded3d8ac2d8822169e8b86aa34eb52969c1243f87bf88bd63a19f2c98b296846c953b89121f2c18f7445095f4ef74c87e05e369d1e1c344dc0aa690fbf78285a7949e56c1546ac4eeb27f8c0e15158c5b5ab796e729479a88f1c5144cb5e2616492d20bb7144c1b425d90939310a1a62898944e6239928e45f611140c49ef8c2c3b1d9f25e709a6ada04d4c7fb58bf69d60109152c82eb16676134c256e438636ed24462618e2ce89abb10ba2b35d827942beb849fa3f4cad0453aa2cb1134bfc5d5693600e26948c689f749e4e4182419ba5941ed3d264c48e604eaab3a48f9d34135241a8610473fc78324e58b827790e307290072031b868456270d127404e428d2298544b6fc59525352d11c1b897e331f49aaa509f21987a7e726c4a76cbef856072f9f9b6b7f5b5a807c11c748e7fd7f67423b440307b48b14ee46e7f603ed12f61a1f3bd9fd20726a1c24ece21a917f1660f4c1e443c2d79f3c0a0b4de2297ce15f2c41d18befe5c3e3be67bd3813978a7b5e7c4113529393068c4fcff6cabf289e1c03c9ed2782995f583f606c69a5cf359b1b45c6a03737a0eb1e784f7de563530c748131654a5698a72dc40c18d8126a0c5a5b2d8c2030235686014d98f61a5af521cd10c0c2b2159ae546abb735dc8504306e635f19e576269b3e4d688815163628b5239964871d680814954f3c478b40e97995e81c62bccdd513ccc841cefebd2d15127e8e87085a92e25212267eaa7c75b61f48fde96245e45d28a1526c9763ace2e05913b864e6078a0b10ab3e41c9394524faf15972a4c79c429f3b31cb1a6531da0910a7330cb163f215b760867410315c66a8f9e3ac24cca95bd61760aa3c7cfea29e85d7cf06e786cd9d0e201f8050d5398cc3abd7398a815b6b35fa0510ae3ad5ef6baf8314ad43048611032d408d5ef6f3aa447619a3311fd77449867511d6888c2785ab62d173f028d502466326b649750d58716280aadf4d395fc44758c8086278c1ed6f5adfa952e773a61acbee816230c03c717594da0c109546ee42822e5f5028c86a8044dc6e2a148140c8641614018100ac330bd0a831500000010168f06a311418ef358071400033a40304630241a2216141210120c100e8442c140302408830241301010060342a130e655744d00402a73b6e3e6a18ad30757aba1f619b858c691d8e2bacbb082cf764309d12c666eef0f4b175dd91dac9a5747334629d61fb51c29a27b62d8f8fb55ddad5d37e58fbac9b4423dbca83f1b76766105c28a530d225a633a0c642bafc68936f52629ca4efb7a725111ebeffca7eff244f33988bacdf40b185eea5fc3863d3fe3d082b95fcfe00f1a4b77c499d4ea23789174ba49e37aefbe5fe0d09dbf5ce3819d176235bde302445be2f9669f7bded71aa95963f95d10caacbbe96e517fc997768733ad6fcd5b4709c68bd31062a2f72353b4d6ab75a3a079efbf337ad13872fa1e2b7e01e16973733d83ff476ed21d0de20f7e2bed533d1b7335e7366b8eea6c59cbdd3a4b63e9ffa8e582e9505ece535fdd783abb351ec9ced8ed3a506dd9a2892ec088afc0a7eb4ff98639a9e95a9655c5a4d638366c3c559f1b0dc9116d93dc22f11a1aa432987d7d32d8251ded4a756541d7c0589c78e6ba609ff446a7922e4ee89292de9a09f42d7d9019ecf01ffdd3949f32849a49722afb3e34ec83f642a1df9e47179ebfeffa8adf82630c23b54521ac37a1ac234d501a73eb457b22c829f15e69b3d2d0046da4651aa8d77f7b934bbb2509adea966f01cd9fef29629b74a90e587d821763d870f59287f34ef5785f079460483b45ecd5d31050674a52b37aaf9b180c4c053ca40f9450c9f55bb750248da5e93aa11720dd4e97863ba4155d1ba99c0e295fada551f4a0671540d5955c28e93e41318e551d4f46049cdf1714b0aaec9012aab2965ed7aa1f75311926db5149a025a2ef77844fc03fd34374e9df558ce15ce712d3c9ad61696b50419118a0adc8c3d76953ee7c7efaa45d82fb7fd84357c43ff2864d138bb2ae0dadaff0d8a41ed54a93345e29f4c48276de5dbb83ec775c1d65945a43757fddd2490ab3bd6982f18fe56de6aca3126aa8a71544f3d29a1b843808cc52f5282930eda9b78aae0ef452d420806a4b06f494c668543deb41cc1eab937a33e7867f55cbf540e6ebb51d65efc05c590c5a31e39aab618ced14e604e5913a09333562c52ac3add569ddb0cb72889ed79eeaf410ea33668daf6abd8734afbec748ddbcfd64db61a7ec32be1ac9620139d81fd6ed8ccd0fdb320110e4f0d1c7f265f523d3b65df1ca99343da0e0bf7e9f949fde4483464be19a20df5c18c00d5b9744dbfa19dc257e560dc4df93f5990fec168cd8435417f4b222caa49d92118e62d8a0cb69c5fd7ebe5c2a6e96763535b5c6a7fc236b1b42ce6e0aebf804ceb8d10c0edd8daf8c1ff83bb8755ed36021fd6ec1a6d4fc086077ec01ed1009bbd37ac41d7ee17a2b9a3b14d24be2d6fded6a7f97ab9ba33c46ec366d2c19fe2de70454726d21bd22279bb62d8f1d2c8c99353a71f60527a248ca91959313638c843b09f176b6dd51382149631b6c590da7cbae8e8f368ce7f6d45b81f30570ffe98633fecf20ebda9db05b2e2ca3faa59d9ad0a2e025b3f3ea21e496c8e4519912d54644ba8c8ab15c3096cb4a846213e4e66b5d25d8ab2c4ec4fb4443c7d7c4c2871dae82407088c3eef8aa41a945e0bdf9a6db88e3b2ee3367527c0c392a3906913e623d85c344ca48a99192a6eeb97f972f7ad42889e86617e46b2248209aa5b8473a88188d6f3d25ec097bbc0e9b19f59f9326399b1c64cae62cb0dc0a6e7609061e68d682c86c11d09c16b6e6c02aade2e0f2beb23f0cec638a3f5f751f6ac1d9153280c236abaa2da70c73c34c483c619ecb0c01bdc033d2badce43215c639e295d12b75535db888329bc0c8467b822ddc3ddcbc4d003dc2310b0e763255f4cf6984474cccb2bef8fe4b82781571e5419d1bea5315c9d324fd8e58467de9474806293ab312a3d8e34d6abed7e176977229802df2b86bfec4ef712e9db8b084d320ecdc572c0934d2847e69f3df959e940bf02a1f25d87dc9bc2e8d46370c51498e438167e3aaac109ea4ff6d23e327a715b10b8f0ae357b94071fc7861dcee0834371dceac661a363e41f367b50297d3eec8aaa89152f49a1e0341d1a5a987a66be735bf0d3627cae0827b6624499ba0acdd69df1d4f8e7d402abe472707262a407a98f0da0db94ccf241f7e7c696975b051842348915cc80ac01c6f846546316a4769767051c95a904e426f0f2006c0b400d85f69d5a9a7f21369d33813603f802e00f55e43f50c02d2027405103dc05500962e782da0de807d025e00d403a0fd00307ddd66089480fc7fb885be65d223a7bbff42326e589d7418be72132310f560cfac2e6a876560a77b9eca309d3150a1953f572513ae6582af789355eacd0e62158b39de622f13b2b58eed911fbcd0eaebfad576c809c6b86d959f5f2d518bfad4b6e4402fdb1c94d10e0ed9a5f98ec3918d0b98dc5d74871c600293144b44acb1aa55ebf6731eac273c27dee56c67092f860a037c03617727261dc271b2854a3781ecd76fcc6bce2f092972004e43f2af6290f4e656520843bafdd4b94682b61508f63986b244fff17a33baa95fc82bde11739e64e1982d379b284039c93335a02d5a64f7967a64e720e02b2de0ada97920cb0bad16fbc6f7f65317dea41b420a86a02a41b7a7e99a1705831db1b513df1e3b8673d180e07dd056112a211a85bc34ded3dbc6378ac4a678b54190c3ae61868978229e856d0d8fa413c449932ce6f8a52a82ec458d3d40844087b33f637966d9c8304bf24a738a2caa99871c521f2972342dac8aa4eb706c703db0e1a10d14c62623a6c2b3a130073c5c0012e65b031dd7e3ab1247831ff4624c6a14d4fd166f1154c71613912c25ccb2fddba5fc44ef190911fa7feceb8f94dd4bcc555c14b29c5d06fea721ecd0016909ef62004f3df3011a5385d0f0f83b4cadcd104e5c528d3f30b2e3a8c989a121e70aa01255c26291287293239137a8d49c54639745e294426dcba070f642513e7539a41b0589a6b4f020126328a965511a83623e9cf722465610b0d594c902fc74800dda515bac5d51f5d452124fc1e73505c45c5e2263f35c1831aa57b940b10e782ab4b4b54444fb22314eb7ad94cf6e8485e45af7303e4421786ab631e0d0d2e967d37c0aca6b4dbe1859c8f16862df53a0b327696f1401c6a60f2789c66668642575842a51a1226c7acc66241e0178f34caf244142f019685902f19cf50bf46052eea93378debc13f2d5a4fed6ac2e5b9887528dd2b453663ac17e2f60b1ea58203910903cf27dc85e60044e1933e5bed82b65250b74b51be8069583709db1bfd054cf2d2a7806982dc29099dc4349a5508e0308cfb8270158688d50dedd31ed0656a103b45a8a040325192ac44173c3aa3c181c6c29b14b0dd6d326fa02f53a86f57e014fd907ebb2ed85a317347f89dc52a130a8c592aff731c0bc1b201a950706026977f77f7474f5060e57958520450fa9187f5a260aa240f90569fa68b57a6525d89cbcb57af0f52a34b13d44f05e74058741707edb0e6ce0172088efb03053f70479eb22a5688162545851ae07f710cbb51a55425410429aa9428a780cd34c46e42d4813597a794be4118125fc50897dcc611277b49c4b80d2dd0c2396b8461b2fd0c214062e7ccb8a858b685c0a7a4cc4b60acd9d0259abb10caf0cac6ba41b99698b5740e832cac51c96d45438a6ce9f09c3e637534a435615ea98e12384b3d2d9e488cd7fb635be02a6974302940023d45d7fcb6450f02d4c59f1d1f51f94361a0da5f11a91d834378342510083266605fc0110ea68247b6d19ed17073782e85c7f317d3e60ffb1c7e90e4b5f5c94cdc5d1e749d4f5c2e78b850b36ec8507be408af5e37301f8b037f227f6817cb096a1215cd64116162302d74ff86866931f301d238ff8d423e453b51242d67af9140680daf1355048b39fd4c2308d008d189c038a56bb2dd662a5e7463629cc37654d3cb88677f49c78097528e327cb02cf77592fa3dfc21112d1f296488976c572a53b52533f5e242251d84075aded1fe40d66291050e315a331a849c2cf40834a1a2330806af080cfa4d573452cb0e2e98101e0ec8661bbcd3902a521fa03909380891753366b27c49f28be9d8f81ef38264e8230c76491fcf8681c697cc5aa429f2eecd6f1c036915f4c376b7abd69658f7d02292cfcfb92b751209c18f912408b7df061801313ace5c0fa08e4fc8a7edad74db8333a8438a14a6ee50c5887f9be0501dad594ee7e592e4ce112513896744a6c858e5304033cbee3a609dc8d1d681540c17a9e239d110e0909e8b12aadfd7d7f18dab8842f8b411a6b7fb74d0850f5dd4b443cf63ceadd4848943b0b583b7628a88b193a5cb66a2c142c9063b12b49829fd78151a1e7d49631f1a54777405be4f05176348da681d726233011c4f371d88da1807301083c9b1772695fa0fb8587f90bb9a2d0648ecdab3ac5cce28f783f4f792d1c2db063f179629b266c407bd68768ce773289b32a28f68066a3da3b82fdf31a2e07084e41d3853d4f1bfc88f8f31146afcfe4ee8e7668fd0ca59900f34c314e9a0671db968ca39e1c256b23681b50dce66e5268d57e63b784cf4a235461093f50d0580025d46ba2187c61b1cb3322468c205d8783a9da876273e8a441a8062098b60a0d8fccdcc93fb2e50df6b6e70026b8b83e4e64d2426526d0b244307ba26b9f8001075c62d5a7843d9175f838a56c2847c544d8594606ceb275056e3b2c3e19d5479ceddfe9f7b8ce9146dd82616e098cd114a0fc1e5dca24743e5c307d7ee3f60cc798649e3647409f50880aedb23888e3c730211674500d6783878f4ada83d2fc0eb81f0a294e73b76dc6c06b7087ad95288c6d5041de786c01c4660d562400fcee3c27ba26da869f25a4aa5e2167925f8fc517f116c3320be2789fc404de542386bcc8c77fded516603e92e6702251fcf681cfaff985115f3440983aac3624df29a34d50e1a0e866b5ac9b5692becbd52048daf2f2cbbf286753886a86d050c05055867ba7adf2ed16b77fd4121e39d969389f81cdf29565b4e7189439dfc4f15b82c9e56dac14c4555dbd15f0a720dd3ae275a85652062fa68d403ff4f64fd330592a2ab9b76342d77b085664170056fc61b0e2e22cc54d276cb066567a003a3c0ad4e3e6a6cabf9a17c90e749e24a56a5c12ef4d82a56456fa8ae9189a764e05b5157bf9445b1bf54f5dd5fec6dec2992ae543657a742de7da7dd480216501249b4bd22f078ffb829092cc1b162532bbc4d7e8a0209a0b75c48d61f420a23906856e489fcdc671e56102f19f1c8609fc6720f08eea49f08d2082326c8c89d0e0da5df8468e8074a20c006e1da503056ada1bc458d140a7771b82edec01ee5ca3f55ac17b7fd96d68ee43dc371c95f6d85ed294cbe0a1f208a5b9c83543e84ca0ae00f2fc99b3648b0bd1be1db78bc311ab7da045b49d826915743b4b65b633cd7313320dda6e3bcc02846157aa72f8ba9790a6155c39bdae6887eba6e85e8a9d2f75e8d24722d9e30119fb668799990381900ad8c4b1f5a379adbb8fc0292efea63bde0b6de8252bff4d6b09a78a2e977a0c3ec87f0e11d9e16eafe21f5a26b21cdfbf8d40737504d2d8dbacd824a849f510838dbb66558032218b1c12fe1cf83c944f4b56ee58b74436e1c2712b80a8f02ed0124be397c21ae5e53dbbc57026b6a7468184396f9262ad58d591e54d443030f53e2192a52bb2afceb61c4ff23a8f3a06b71fb7338d24d1042765bbe83af07db032ae77f385b932842420e064b9e420ac8ad603090f07f135820131e449401485d0f83a4280c83b8cb7ee4687da2f1f43a8076fd1c82c993d0ef575aecabd876e0feed6ead6ef658b0255c09b6de5b98995f0c137fd65971971c967f9398b4696da0e80ae52753a18a93700586bfa5c0d74c2e171823bacf40984b983dd96a97f9d243673e66d4a3f63515e90f4250f7be9ab5ff08e850186a7aba63e352822198de44f1b39a927311da44413a9971eb3942f90139d14e0386fbdf7f14f7effa9df7efa379de61ae7ba754a979de3a5a78fad95eabedaf54087bbe2f55e34ce0c2f772490afe4824d132021326a17f1bcbff8b8f77fd88a5101ed9ee37272baacaf9608badb2f71fdeb5ffdea373fd349ee38a55ba6855b2aef5c0c8b0ff2171962d044d4c79ecd59230b7efa251dc6028bfa17dc63f2d497bceeb92f3ee99bcfe786f35f6b523a4835165236c69a90350662be57e6646238dca4647118ccac8d1144599cc82707d35fa9804b94b6cf7852139b38b1539fd79cc68fc604e39a6a8cd31f1b39f38631261dc9a7360dc82225daa44103a9a82327eda44621b92926079548d394b335a0da6413315856ac85bd1c9a8bca399a6ecea6c676ba6d25150ce96609bab266c6ec9ed94ad3b23b2d5648f99d4c928729e532addc4c895df4e568aa199f3b2ffc3ab55206eba7dd2aa4bede4a85fab4c18d1b679aa94e61326699a5989dc798ca00a4a49ed294a0154924a780242822899264a71f19e9933605a4eb649dd5002d3105bfd45dce71a573b775d8352ff5b6db3cf5d4d73fc7eda77ef9e9deb160012ca37ca2a0886e44519640742143b914a0835648909aaa9c6d868920223ed2d4a7ec0aef7be4b35fffecd79ffefd67baca09ee74d25da776e3e9de76ca3b976686a7d92332fb373227afc0221abfc69033b5732a8e1501ec64a475cd495c7536579ceaaa73ddee9cd79edc9567bdf4c92d4932c524411379d126073548448f14692fdd9b4f7fddf3de705a9ff3cc606e21e54c0f3ef9c36f7ef8ab5fff4df739c629b2567a985d88a883ef78d453dfc15f2b86ad5b07a49096d4d306c142c90c184c6c69fa89a009a2a82224cd094709970300847f86c8e35f7ff3c75ffd503739c4852ebad4ddcc4126fa484729b9a8420675cb76e569dd77c6ab9ec6638278776286131cfee487dfd429d771216957f99a80d3f130a597b621a2289db379e77c9edff9af42d073b25f550d1ec31446351119e9273d0ac9a73219a84526eaa4414769f5056455dc5cc239e12f3ff9cb4f7ef933dde70c779deea2f35df1ac771ec362cc543b401e9a4055413fad3f954d8241e62c9221cbd7f5dc67bfdff433373fd6d1941d3364653abcd096c593da0d85f99a72eea60d54c4293ac494d8dbefe75148b1de962397585e40e605980a3d0d1655fe940bdebee99f8dd28c873c333e34621b96f777c3c936001af223b6fdbf54945bc04852e079b6daa52763a73c00138329efb3d4a34c246b704ca2329a7ecbef4dfd9e19f7855ed0168d111b443eed52b8ecceb4be4010b87d3df4963b52625bc6db5f3a94239c1a7bd973dd5c63f8c4acf62dc56b1c537d338f36aa93e20cbd3360ba9fdb489a09532a2989e5809d6f4cb2364e47faab3992a9fe1ecae1aa829b8360661e8f7b5b235f1955e0a211dd678e3f4c061e4edee2763aa307941aaee49d75ff0f0bed6cadbe505b20331a7f69fe782f3332eb73033ee9cdd4a3a4662a1caefc4b91a4ce682caf20cc63c22b9b288c7d6a812b2960d5b8dd31406b4e67de07ca2920a15277ccc93954def52f8684b1099ad3747b293e01d902552f2327ea1e119d985b133cdca97edd522e2930cfe286b8f555b7d2abb4c6fa115908f0e06e1ad51060f97a6531b5937dfa8413f7b872087fa5618a0abd41ac5e3fb1d03e24d3f28be346d66baff9af45c6781533fe23360a402dd925dcb011a0a20c013f9369cb52e58fa3b92436870a480af7fb4a456a8144e103eba587e01ad47441bc22d69fd436d16a4598e0b3036fc870257f35b9cc2039f2589ee9605999587f1ad0327152af0a021a3d1ca0e3bd02ed2e719b2e7d07cd27161eb647cf56fe513261f0063d859fd8ddb31e0cecc7ad5cc1c4faf378713a533bf545490a4308eeca724283679ec8f94b67c5433135ad166f14ae11d38873c7ba5ec60001668067f42f6bc33dc70602c31cd116859ae31c07307d3397440adda5fa1d74faa2f295ac238de0eaa3fc6e59c583864d021a6a40aa93e16f7cecebe6151d6b155a75840c80682fa2cd8f32352c6a1d57c45e9bad4560a70446086ebe0a127b30ffa13872270c0e71f4ee0cafbb7a9a6e2a3fa4b802b36f42ff5be16a9e11205d004d241656740c2b11e9d4a463c951b24aba4d6e28469859e0d714ff44c5e75595edafbc90cc38fb4ce21cab306aa5402355db7679f76dc5907291e1d12d95288be597ce12b890181134d51d9e6a485c6c88684da61f2a7683b831764f989bee47f26d1906e0acef0769149f9419957f80ee52af077a7893821418c94197506bd85d9f09084d35725c810dcf3e6098062cc16a8af4e6ad5f28d720504deacee18bd15192d1c910163da8d72165ad116f9702474d96e4674c4db8149628c4a701b8d53400789e5eb69b9c0b3557333c64515c86dc14bae5240541055a7d82d65b95d219889aefdb37df46705026262239498292182a01f847e54b5238804d404a3cb5a7c246700b4f5a9f6f4d3464b4df773e83e3398229e3b538e6cb626c0bee3dafce63a025b400c3ed1ef211d37e6bed7f39cbbcd52508b451012b72b99d411aee48894294ad3189840d4c8190c744de8fde1c9e41e97740e3301d48be51ab86c90c66bbc27f132844b696531a965a4e6881da9fbbfd9d54b1ae96b8d84ede632d5dfb18cfa17977c5c0b3ae10d35d797c07a4e082eacaa53bede6de60d7ae320a8c394f38de7f122c5a5a4768ab77017ab9362475a2c24de5e95dfd078fdc657ecc4bc8c426e45df9f5f831ce84f973f51c921afa38fa5bdebe7be19b872297c6907c7a1f4518ddb0fa5c9139021c4b21228ff0ec389949a10c3402255554bc66fa64722d80666eb15ad576e40ca4ac51ccdc67014fefd89d946520608d7dbf6d98a85b6c362bef0fa8acbbddfa77746d268e6bbcff930e5c71fae00cdec4b327a78210cb6ca3013dcf22cf07023f78ff450a620cbf9828bf3794cb7b1dc7234f6f6582fabfda9f61e1b33b66ee1d6b7b2ddf83c87f3ac62012ec0bd4840e2752bebf3db171d8fc50ce2fdac0ad1eed41d25ed130907df1728353acdb571ae6a91ea12a30b62bd389f178e83cfcec6268f541832d7513f105cc712ba5e23f396834b98f7991f02a770e96132a9c89c6534e98b3f95059f93405dbe6b2c55d6e66c45ca9998cdc9dd4978582e1417277a78c164616ce5e13d593ec68bfdaa7df930ce3a5a81ae7a9e4fc473b4ce9dd0bf35c7ffdb03ad02d24f986504fd618a3d42f22f7dd1bac580a45cebc2c1446f5ea63644abe476606d6205ba6f45e1f2f942e3a54f046af7c6a05d33cd12af93872bcee48603d8b95d622ca66a71ef3b311b2c55d99a2022d879b748537d509b6657692910e21f465538f66a68aa49ddd4115e514ed63080054432f7face02b85753568d469e7528a5818800d60c5760696c282c2b425f47dc7c4942e3c0a0640f2385a5e4c7f74a375ed5a665f63a6ce3b7cfa68269b5253c86da981da86e6ecb08bddd0ca18689ea59062eaa78fcb9ca4175e3269ac05923f979bfcb418726252552c724b5fb94d882ae6ca9178413e6ae55cbdd21f0b9b2642c472a4a7daf4d4380d4541eb1ecf2761905a0acbd0aa33609e15e6a5a41072e9dcf1131f654621719d2aa9372a59145d038456d4f9bcf851f6242bac402cbb2122e6c03cc0258362b6c1dbe3e3c9a2a9bfd72655a3b6848b66c44927f829608082f6d9e1d02066c66d921a4d2e3fc1433710fac34f0a91fe420e187d1bb6b512ce84086771a7eb4d3d2816e488f781072f9f4c773b19a3e85fcd57c2ad3ff8fee197fd9128e24034388ca45d823a797e1bc7ccd98b530f06850fec8e2e48a0fe91ba24566e8a71473dd0bb2f9d8e4aa499d9b53466d30bde5555fbf3ab9236893e937475a7ad4a446a92a5a932109af42f6dea6719fff2b13180e548883c1cfc9d4c72c68e4fdd338305cdced2eb809437170faf54227dd55c938963bfc7edbc55f0d547ed15317b022fc69bb4d5ea1814ec4edd7dfee4048a46ee48527b6f19af964afe90fd0902e57cf8c2f96275bcb1176f549835eda34089d0b50ea344e589eaf3500001a59aa3124c2d89211a01141834c2f10d2d34b0325db065470f61948a40ca0558fc8220255cf68076a314aceb7f00d4d44f420a782524397a6e44683e6320d51e9f2236016d325b8008e2dde8332336ba5d4f8467ed61e09089dc20b9b2dc654e4d6fa2e8077a61085a127c5ec664c95e574a6c2ea76e250d6e144dbf817ff9c14628d97ff3c1668bd6f2b942263be2a84298d98d3daae4b4001d775421f83abde944592a9db93a45d82a25218775c96b67cb5347e939dda71ea25fe77760eb623123edc0236b5211fde5f5d2ff708638d0c8340e333b3db2e2144204474944252f6c002c182169dc2185f01a77c0818b1b312a070cf0b182b1121f11e4da6c989ec99183db178004497c1ce64870808fd3401ab6ed78dc5e903142e5a956490d095d7fb9f8279a9e70fa213c1543039aa54267e233372bd6bd038c58e17be24d4ba79035d83d1b905442d8b3c421be09a784a2d6526526b1531c5373ae57b8cd3074f4525af5b6c6b26c8744c568c1918c6e4c38903365b2d198a3a179b35278a84a3630325548d8376e11af4ac04bec04c93f2e37cd7636cba0def24be867d145c1cb6f77cc1c7fa4c7b84c409eac186a2cfe634366217a32e844d7b183b987f4e78d158bc666e25e0ef0bec1d7bfa05406723cc6c8faa25cf4034e0054bd93ff2aaab11127527d0be716c68b28c9ee0c2d141e0094140f5eb28428908a921fb8381fa16c5d3195ac035f65fd13db5e39101c7c10c2620f74559b9830660f317d754103e6dca9aa28ad920abe64612526d7e41291d9c8a77a5cb365a44f413ac275ad47513378fdeaf071f3f1916ac8b329823468ce78c11bde3139ee4b1e278d6b98c6546d1ede795243ae88dec042f3539f8cef838de6ba3aae30186952b28dd53696b271697375ba19cb2e44d0aa8d2ce966a687ffd6584033d80d9162f866074805374fdfdb4e3db5efc198c06cf6929b2d856ec26718a3091e8414084f39e4d50bf98cb50300011c794d9c0dc9b74dd0a2a7c106bac8aba54f21089d75e8232cc3f90d43dc58f1ed6365107e089788e8522adaeb05a24a5c98eb7f63caef40761ca2cd04fa308771060d92d457ac8e631642a38de981452008f38e9019fa40b484b61935b5185a4d3f31a272ba3f1cdf2eac5c08b0eedbfee319a1ca2e8b8861004cbb84355a159ee0fbc2ea6752aa5e4e23e925b3375ccc7cae3f7c5c305da4fc547f4b3269564b3a7a2c383491f0bfda5bf0999b7adc9539923653358184fdae2152a595fe7cff34ecdfb1307aed180832f35b8df34a242cb0ec434d35b93b62060fe00b1af36be8fcb0168e82ef8f1a15b415c697ce7848b80032f39e8efd6a82340941de33d42240744f5766a707a80aae726eb77c149e6b28c01b0cc1389fb076f94cd18ae06e9ce4e30ea2b8bb78a2edc901c48ef97ef7b21036cc0cc389ac2502ce1d048a0a3440b2a4d001ffffffffffffff7ffa0cd5b66f5bcb6846a69424bd94157229555ea69464da29698fe89b743af0a135426600fb9ab9160a030b7a0a231f18b971466e8c74e0c60807466e8c6c60e404618c5ce1c30d87135b3f13fb84c3d20d1f6d386a6ab24a3feaaed2596ef0c186c3996aaef1bff62f3d143ed670505193b67ada1f6a38775d3225e5560f3bb1a6e178b277992b7aed5bbf2a7ca0e17cb289f99565479b745253f83843f2618693e6ac4b6549e76c5486f38dca24f762fa3291e1bcb6962e95e027ce9cc67016df4a728a290b1f89e1242921543c4cd06e52c2701ea11f5a53aa788860386e496a32daa8a0b5fd170e429a92c426db0bc75022372ee62e9ce64bbace7a9d0ba70c32463687be857398897e99ac3616ba164e6792f80f11efd7cab370b070caa26f4cf94bdd5838a9f2b9b9f7d8dfbbaf705282495a5a632b49a3b6c249876fba2e4d158e71a3bd292b9a0ccaa6c231f9e7564a1af376494fe198645e8a8b0c6f5b4be1986e3f7349e849f21c85b35d126b1b2efda4140aa73021546a7cd1f096271cc3ad96a035f7c74e9013ce274a16a1dea2bf62a8090795f5d69be4cb4a79269c52f0513f193e74ad4b38c98c2f5d16259c4bac2b96dc25625912ce33af57299eec0309a724fd557e9d7dabb8f938c249592c9342764444643e8c704c5265459e497d169b8f229cb3c4fed8ff17514af3418493c9cb9c5ef934fbbc1f433805e973955eaf47cafb2184f3ea7dfcebee09f56e304e6a53f82ec104c6c9337f959676cbaffde25c39453e643a294c6a7d71f8322563cf663cf97b71521354d09a2989a8342f8e27cfbcefc56f9bbc8b8388b0d24b4a2c71ad2e8e7523bb34a98b9b7271cafe31192db2ababe2e21cba4dfec4cbcf4bb7385818933749556cd26d718c2b49dee09fa5d5aec5c9829a60a52994a4515a9c5234dd693f26c65c6671fe0d22e4a918e2952465718e6d93328919af62502c4e77c2e88dd655a3322c8ea554b5e852d19bcd2bce96c3545a1cb5a0b5e28ab3aa495295c514b2a41567576b517a7ece3667c5d12ae5d44aaae252781547abacd179e237bb55c5e143598a61317f932515a75416d65aa2679c878a73499a5adbc42c890d4f7112535142b5aaa98c298e7d5ac9c29ead85a91447fd5242fe872a696348711267f4a228f9b48d4671cc70674a92636f9615c54909afd427bd89f25f128a93064b97ef4ba0388931fff4251f55dd3f717039fdd874b27aee79e2b81a776472b1249fec75e26049fe32b18bb5a91d278e9725f2e16f6d2adac4310815ebfb38994e581327bde13c4eaf89a7b165e2b4294c8e79d7cd6f629838d88bde8e25eda9965de2a87b4ad6eaf92b95b7c4b14c89a39945c61679254e92a9291d6925bcde9438a81bad97ea9ec4e9ac04cfd4a8e9b495c4d14386894167799a301247519abfa784eb4b4aaa070f98042337cef8118cdc18e1f1397a30080c2c06008993647297d8e8192fbc6ec1001e710e2dbf31a341effc260c0370049a31376eacca77233e51fec470d2995818613c7177ff4f6a3ddb1691501abd5489125a52a488e47451febda2514b9588eb6f5d36c858491222be954be2c7e91065a524949a7b3f593186f82cb3a5f1dd540da0102719bf742689276792d61e67bc0df0c940671800210e4ad2fb1659f2253488938a774a4c9231e6a9200e7b61574cec3669ce401c436f9272a5629a767d850100e27c1bc26cf48c1e65a63f1cd3a8d7fd932b465ef6c359541a25f3e8134455b40fa7b4cc223e4b795830f9702ea157476a5a33297b38c991ad8d1ebf1b93490fa7d8644a26d15e344c9f8763f434d9ac263f667c3c9c2cf44509e24aa6b0fb1d4ef26c5db58893ec3bb7c349bd09a6a45b9594ccaec339bb2c67c3ef5d55101d0ea66297beb59394d8a1391c46c808d374f97a632c87839f5ce25e6a1aa1721b8763ba86d3ba967209420a877399bc6a9119d13ce51b0ed6aa61554ef9d59b6e389e24c325258c490d7769c3c96eb39edac973a743361c4de6a5ce2d256b389eae13a3993eb78c266a38a9b33bf9c6772bfd349c4e2a8ddd7acd68361a4e7b1953121153a9e33bc3d1b4052597a49d194e3a2a891e531a7db3329c04bfd7f9320d198e5df11cc349479b8c7fb793ee2b319c3594182f94a484097e0ac341aa7ec92cf683e118c39864826aca522aff8593646619544c500bbb7be1fc591643ffc6fe8b7b178ec15b84fe0a9b0ba7241bc2b44bac242adac24966f13eb9620e25f35a38d6860b9b47f4620a67e130a72656da68260963e16cc284357144655093af7012d5de7283c99661b6c21a0650856369a87217b91d6d0d154e624a2a935495291c735ca7be061162a291c2a9ef825dd25549d35138bb265372b7a7263941e124c574dac9a93ee1543a7ba2a87c3ae1b05d25944a72db096e69c24184e5ca0b35166b62c2d12b4ed291513fce5e02dac47a339a90124eaf492aab6c254b32310907cda3bb3126241cffd2b465e84738fd5a9fa60c35ba4a231ccc55f387972449722c82190320c241bc5fe936299e8c493284e36e8c97a452dd008470ca3531fd7a86c6178371f69051e664e6d5e82a875a4100c03885124ef8488bebcc9b5f9c5ec7040b37eac2dd452a08c017a71a953b429524460ba75e9ce3f4e41b6925e2c3c48b93fc06b94a7e6fc94abb386670f1d166255dea5f17477b0d75edbfe7e298339455d2ca30a1775c9cd4d488f9960b6bb5dfe2f4265c8b658ddbe2242397ceaf4f3a77bd1627e9b62999740f252d4e8be3575f7a788c7ab57c1607115f23ad646c0de296c5b145f8796d9f7cad742c8ea2a6640cf7ad934cb038aea9f10e7982cc09a3571cd6645397ac37e6cb22579ca4b29e3bc9475b3a41ad38a88f1bb52e3b4935c48ae3afc8fe4a6939eae7551c9378b1212acdaa388909f78a5943ad9d702a4e82676c090d16debf41c5e9425ac50afe27e68c39c5d992a7fe8b698ab3e5a8a0c20525a6c94c298e3316472bb30925a9a43888d50f3729a79ed58ee29ce1d3848b7f1b4c92280e6f496e8d0c6eb90dc5d9f7d64bdc5a41718c260825f688fa89631056b2e95a90b312f5c441777dbd45d7989567270eb24f54499513d6a1e4c4d9e22f3346771307317d7726624bd4333571923a9aa471b592c7cc4c1cd4a9b64cf26fdedc25264e31e5ca94c664b2e45fe2dc5f2ac5baed8693b7c4295ddf76e39230b2a9c4498a279a2cdaff4d6e0b254ed1e4d292e992cc986512a7fc27ef43eef8da582471b02498182a76891d371287935db536637d9722248e2705fd5d62a4c5bee023ceffb51b83169d493f74c42953eccb16e35f794f1a71d0b3f14f126525ef33e2a4de3328a1441dfb781127b1a41053dba352d653c4f104ada49212fa44cb4bc449053159a6ad5252a943c4c13277e532d1848ec80e711059b7611733439c2be53df7124c55ca17e2e4f657e294b9e956008438e5caa14ff44bf225c112c0208e69d34afc9f24a65435050108e224959422b2e7a4b27c0ac449bf6e4ecab4bc363f208ef974bc82b8742373e60f072da533c8912958e9d30fe79274d8c5ae68a6aad2876312c2f6d45d2a0601f0e17c79def49ee85582bb7b38b8e8cca79228dea7cdeb8200f47010ffcbf562a7fa269f87a35cb04d7a43d66996f17012947cd9f0a33b1cc49fd767d797d4a5e911beda9f294d13369c4266dadc186737e94a6f0a216b38893b3152438637e99d1442d470b8d158b51b4f9c59fdc2109286f36899cb6825ab0971cf05216838998c7ae2871255229b628290339c7daf6b4c3ae91c6a4b8310339cfa2a96c9157284f65586538dfda5923acfa1c62307651c192164389bbaa0a2c617a5f2336338b7a6a692b57975543a879aeb1823958d88f18172060f76d420440c87d1ad76278e8bdd57d602216138c6ecad8c7e090c47b7cd61794e92997effc2e1b7429c9ee07951732f1cd34e497d366a4efedd85b3b899fa9a4a61b96a2e1c5e37569efabb859385b01b11a7b245ac5a386a960c6ae42c7a6d998563368b77f1a4cf935ec1c2312d7c9d9aba8fa77785a39cbcd8a2b394ced5c8601d2618b97185102b9c842ccbd427d389ab18528553c5e652726cf81acb43a880db8490299c847d7b3f9dcd920db621440a2749ec9f28153d53330887d12011444814ce5af2bc95249cf0d726215038a9d1165cacce734b5cc78790279c464993c5bc0475a576821a93694aea1935530e93343f0869c2b1c2a524f48ac8b7119ff1233015c2849398969230aaa14208939025e0a84088127a50202409364290308190234420c4083846428ad0801022f47040c8104642841001906038302000f20b1c2320be28905eec483ac6c0c18b5d80e8420220b9c03146191b3881034070b100905b4800c4160b528b0380d0820120b36800882c1e00128b0280c0028701405e81e30120aec0310190562400841538464056d143150e00494502405081430120a7400088291400528a1a20a43000c8280800228a0f90501cf76d2de5ed6d6b3581e26c1723e72fdb2a9afc274eff7227fc29a5e450419e3869ccea567675e218febbb25257843639714c73a14156ec8fb0781307db74b1449d65d8d368e2d4f3d617d4a91ad566e2a0ad24d1a941c593b7c5c4415f93183dab4e1261b9c45944de2ec33777e92d713e25b7a24962be1a79254e49989333a8d27ef9424a9ce22cf749428f74ff2771f0d53a933bd784a5242f0091c4419e67a8afaf5259932271589f935a9a3f244e82dad99396468f9cff8883bc2073f95dee37933be22483ca6ae659a6e5f4461cbbc7efbe649a11279399243d77b7a1d28b385bdab893b4f247a928e26432c5cc25b44689262811473579f7849111818828b14e548c39c4694b7cb3dfcf18e220dec3d35d2ed33e2dc449f96e7de97f9d3511e2982dc813e59454f2c9e720cee9195365fb934dfc057150d286135f326ff6409c7ee6bb4ac6b3be1401719227db86b8ff0c9ff9c3c9a468919927ebc830f1c3b942a598566a8396a80f0725a5125b1945349a7c38c67ed1dcadb77126b787f35bbc660da6445f540f07191d9347939687631615845a8ca941be058287530a355eab22e73b2d903b9c3fee4efe7dcfdea4eca0a6a46e6ae29959b389e5bf98de4b3829c5280c903a1c5c93302177371d8e7362eaa4c6e97a1fcde1a04abdea2ba8dddcacac1c10240e6603040e279b0bdd5ce3edf6eb9ad52c989cdd18a9f364949103312c00420079c3f1b28f90754a4280b8e19875972c4b4611eb9fe100481b4e522ad5ae553d1b4e416f29bbd51bb7f140d6701ca1ea37d5b2852d21881ace5fd9921a5132a91c13240dc71ce39ba694beb698540a466e8c140a466e8cd409466e8c9409466e8c5409466e8c1409466e8c54182337466a042337464a042337462a0423270863c478bc0e1d63a42140d07018dda14c1c57b97c92a02440ce706a13b604998f7b8811c688255b0810339c2d892732322fb88671448c300a1c40ca70d8d4bb9b7bb4497629329c6c33bee4b2662a5192982f808ce160e2ac7f8c690dd7ae1740c470127df355652541c5fec37092a26509dd556eaa3e368080e124462f1bdb9073fdfa17ceb61a27dcadc971a9c40be78d161ade6c939865d38593129488d8124df6b5940bc7f6dea4c97ad30571d9c241f499649d23a485a3892526348da5a0abcfc271844ab22841432c9c4fca962bb59885b2f90a474d7b270923949bdec50a27714e56924763c614f62a9cfec3929a122a1c833471cb945a6fb1790a27f94e5cefbc4f0ac733cda2254c5241882a0a4713fce2a4599c4b2382c241f32ce93d399494c2e409a72ad5a6625cd609a7f4e273e26d86693d9b709029ffa924e69109a7b61127689fd11027b38483cc1564f8bf92fe2754c249cf32ebda8249923a9370122e6d33636d30613a249ce289a9d495b839323247386a3a61e36f521be124f243f3e7474d544c114ee2a6c42a69345c6e37875a1a40887050d2ac9a8f92db72cc219c5ccb54bdd70d2284f3a889cd93bf66c2c7609cbf73f4af0825300ed24c3a259f20fcc549894b25f64cde249bee8bf3a52ef994924a687bda8bb3262905d72093d8248fbc488c27269d298929bb3865b52e2943575e88ac8ba35c3e5df272fee6b47271b6ef92bdf40851526c5c9ca458c9c3c4b8645aa2b738953457d26917b7bdcb1627133a2a449c381b94682d4e5b42cc85bcca95f6a5c5f14c84d40bb59694aa66714c9b84a8127a49f0f96471ac24888b26f905b514c5e27cfea725325eb038588790237abd571c4bcc596a9953579c4375cbc9df250515de561c84d624c3a9b4277e36561cd46fa6d5acdc4b515ac5e1f24bccba49528278af8a638dae8eba9c92e28272e81ee2231507517772c7bd45299945c54126d9f28d293b6595720a2e97e8f9618a93d0b3490e19669d5ef928c5b147658f1487d59079aeccfd18c5b331bb4eba1a4571aa34f92e6a4a9b82ce84e2786a44c9a2c4f6018ad38c2a39d2a44926b5fdc431540591b12e26abfb3e3c71ec18db30794e09e373c6e0a31387b3ec1a939422275ca9820f4e1c5384bc6896c2998929e36313c724cc4a547a51e228f58c0f4d9cdc62256da723c3c7b1c3cb0e3e3271506210276a8f9f8d4ac9a1c638cac8810e7e321a75f0818993d20d7b5234e7fb9939d476b8f5f8b8c4294663de4a31fb3aba1c6a36e8c166148f0f4b9ce4ea529b5ddd31068f3fc18818618cd8b0f151898312d66d744ce7d6ec92f8a0c4f1eb4db2fd92bb27db8ff89884491022e744a8ca8d91911b237f032e810d1b89e1b0427c48e220d4ee8fccfc717ee11c6a47831d6420063e2281ca2fa976a6af27e750c3a18331fc74304607729831060f1e2349217c40e21c2a7c89776ea9cae0c12768382ef0f1885378f35399d2e288cde215397c38e220a71e4ad966491f191da711e70b694a0639fefe96c48853eeae6a1ad770000d1f8b3867905bd22ab6ed4463c6dba0c71063e4c68818618cd8b0a188c3768ab29264b8d052799851c6181f8938f98997e9a493cba156b7860f449c43eed49879d8a5987c1ce2f8267f95b133884b291f8638ae08d998bfc44d96e847214eed27c5c41cbfe275e65023c4c7200e1626b79f24d49f1ccbf02108d66487ce55cf5ce650c3a182f808c4498ad91b5e1774e9099d8c1ddd2333f10188a3e5d765d8ab8a7a31c3c71fcebaa5ed63368d8c92e650cb337cf8e1b41d2b523fb3895f42868f3e9447a67e68ed3269e550b3c18e1d39703c193e060e1d3bee460e33c668410f32c8d8c0c88d91911b2366f0e0cdd0b1e3c6c88d911b232362843172b0acf4e1830f879d0da6ff94d20c3643054ef8d8c349a8cbe5262cf3949eed430f67d5f74fd37e51810d1b366ca419427ce4e164e72197a7526b52f22337464a3022461823685ff8c0c379762e5438afacdf780e1f772868586ba7c55c964c8ad10f3b9cf53d44ae5f345327bb71f8a8c3492cb1e392e0afe79fe2a191e0830e0753d18b63ea6ede3437876392944e8d96b1c4e63f15f12187c3a9a62959b7bff1f01187b396d8ece7e1a1623f307cc0e124cab58ff7edc8093ac3617687898f371c7bc4425e5a92574d4a04233746423072630404233702fc8e5a0cfa80c6148e769745690525c8e4a9144e9b7ff3f956cc860d337a3c0dc6f012c00007106844e198ada667eb7bb9c29c43cd4c0834a0708a49f8f79d122cc7f312c000c70f683ce1249b4a90a6524c26ee33a0e1849329494611972b1a4d38d79d29d9360513c274269ccbf62c89269bec287b09c7db0d524ee64396565c500218e0c0414309c7374198a8fb3609a753f2a96e5fd9deac151a48385812c6664f904986d11ee12c7e422bc534e9dbc48d70b8a0c3ec465f28932ec2616b4dbf52de89b721229ca4a09ddf6fa24c260d8d211c4e95966d253b3bb5718086108c673b427b0825c138a6145c47569e92f7c400e37832649986978bbbb95f1cb36b3ae14d92fbe23c5e49c5f1dafc4bb15e9c33e83a41a4e539d4bec720a34711e1c5e146450975e2b786e973a8192088ecc26ca4882ecc06e6c26c88e0e26e7110a77753491394ca56d9e2746293b0e3fbf1b6772d8e7dc2623ef1ab4d9ad2e2342605215f92f4cc2e6791d05ffd15941894c5b1b408253de35990ad627190e627ae63da9a412a028bd3c80af9bbf924952fde82911be61621f28ad36c524d212d4593d22671c5498a498f36a5d29c8dee5a71163d51a4693b89b0e2fc1a4365ce0ce33883871965c9ade2a0e6b2c62f9d20fb4baa38c8cebd0a26f809d2e2a93859bcc9169baeb1ee46c5498f0cea4edebc142f3ac54953b5a42b6531c5b93596945aa62ac5c1d5e2c9ad55163e24c52953dcb4e8c9b292e446719241cf3cac2f37bf268ad398ff66a6ddcca1f6501c9449196fb35776091914c73c51ef2c9ecc278e66f5e7967fb399bcf1c4e9b7d4f9ef959d38c5e5beffb80d5e25e4c439e63d5c9374a944eebcdbc4295f8a6f66beaa133e4d9c8230f94b641017d52d13c7f02e9d65a2df6e4961e2d89b466fb8b52e71dcacb98d39eb46cf8603229638f88d9efcf22616635f8993cc32df6e26a52d35a2c429ceee6655094fe2204ab68c248ed62a26fa060bafa546e2bc9a75e3c95cd36441903885cb1eadda248f387b89dd3119bf88234ede6f7248ddf9d32726d28853bad25523e2b684ce4418710a424e0adafdf4ca881771b6b870fb6b1611451cd62fc74b8b9688a38ffe12264931e557ca09c21841c441096f7a2575e813a91fe28d9fb5f9475be8d710c7e46aa3d59bec8248214c29a8182649624c3722c4f1a498bebd46b68d4c27426410875127534a262ce50d13411cbbb2bcca8e83ef67815a4224106777915ae15b6662c96cd8b061230f2280389828d2f7d57b9492dc3f9c64a67f8a0e2196aae382881f4e4a0957429c942dd5ce7d38ca860e152b9e7ee6a902b761c3ec0c227c38599ba8261b55562eddc351f4956c1c0b1935e5f57092269fc7580911bafd09c2180183481e4e5fe1a458fab9b15b5207113c9cdc2ea5b4ee36a7277c8783bca53e59567454ae4ac40ec768591b43c6d9a51c953988d441afca2429e12d480e2274385e9f729117c452f64f640ec7771131a63129417e79661091c341c95632d65d2a5430e370f012f366704d4268db6c1944e070ee10253366cc8c15cb3215183381c81b56135564ca85d6985b371c6dd3a7c5bfd1f472f206540c226d38a5a4a2668e28315caa70d8e851923b88b0e1ac6b325396934f6799d670fa8d6b62467bd57032419dfa9bafd6947b1a4e95ab65bd82b08ca346c3e9478ec851a3339c94a05b72c26b86836f9f5afdc7de9ba80cc7d319f47ffb34bf25c3e94d5becfd8d217b9a319c4c2e8d63266a4de58b188e5632c6132e9649cb62184e41962cdd167c5bdd040c873739ca2abe7689a67de1f0a304dbf4ee4cd9a38817f64b6ffa6ed2aa05912e9cd64c4bd2a254d63d495c38ad67fd8e959f55f746640b47b3135254d4fc64aae450238375f03822c607c8e0316eb043c71964ec38c1bf07f67a7840440b27d55562666d9cabfd3141844816cef61a1b6795b2bb5e3c78d77186c3cef620828573684ad1150b96478d573866497236a9d01593262b1cb4469ca585b16065bf0e6cd8e8a16347bf0e4ca40ae7de3b930dd3a6a45ca2c2c14b434c99a07285ff0d1544a6708a932a8a25d1c24cf95238b9855b13936dd3ef1d85c3c88d6ed1f90b4a7f5038a892d32d57558c5aa50462843162c3468a3ce1a032a508d119be6d544e48d06cb2586a7596c9ee2599901925bb4459c964130ed2f6f747af4999af34511061c249640cfa359758e2b525b28493dc54a736abf4aec8449450449270da8a293a972c35264590708cfd65e2f889aa2725f1400e6e30e281148c98910312e8f0800d1b366cf45882c8114e1f5e962673e68ed1f5608c1e37031123e840a408e7b0efd00aedaeba1b8640840827a93474caa8d634fa3484d3ef969859d6a418a31611c2714b4d1c374969ca888371d0632342fc7b6598061847b1d4606b7137baff8bc36ecc8be29aa23bf7c5316a8ed1ab2284e56e7b7150f3fdb24988cc9a9517a764e3a761ecae35dfc5714c6ccfd9b03267a52e4e42b65606d39471ffcbc5c14a8a6131a977bb19b98e1cec289441082e4e25985f5293710eb5bbc5c97285cbe0a59249a1e232be070ed381256284d8e224a8873e0d5fd208b5e909426a712aa9e66bef1bd52a3a768c514a10428bb39cda397d5ba596dc1c6a76488290599c339a503fff527225c919591c5c672f5ecbb4468989c5f92c5fd025940a71f29c851058a8fd159ba47968b110f28ae329f16149bbfc6787ae387b681e2509231a5a9484b4e22406199b15a7de50653d428ed88a0959c5299d8c16e4595a3c2d5310a28ad3e691967d1e37c0c163075e10425271d8a0d49970ba2a28d1e6505b1e84a0e2a04945996849ff4bcae5503367849ce21cb24f9d4975bf495232c55173f642d948b1cb92529c4e1a2549bac154d858170821c54994944c0a2a77bce2348a83bf6cb8ab929498b4248a6310316bd1622914a7d4136b1d665e6525509ce48cfcba88490de2ffc4d1edc27bc5c97739f2c4416f6ace4e113e22f54e1cd594d455551f2589ce89c328d94d8cf71b1d9f6fe22447acc36e35a379ad8973c99bc2ebb3cec449ea288bed109617838993a4ef3329d56c164e74895312bf4c3c69664b1c46a6d15b9ae34a9c644b9dcb1c6766b6a1c449d0f71451d2499c24d5dc5e15794109ab24cea725afcc922468b61a89533a59a255c789252d0a89c35bb4acd313d4e54c1f71b824bd6ae90d19ed4e479cef2d997c296bd413b311075517b95fbf27ed4932e26ce925bf89f323b3ca451c46d58b3a6983dcec8b224e52bef6b50625e2b4264f7cb6c4171b0b220ea2fb96d6ba5ec23fc451cdd4595d7745511be298640d51b2a687f2100b71d8ca19d19784db9e248438fd6d50d7e1a70469728338a5cef64c0bfa44591027bdd69fa6e42641c907e2ac1737378693cd9703e2a437e849abe926c918fbc3c933976552a93de3c90f67537e72fa57f5a147081fcc46c81e8e5fe1757e2ee68dbf193b74949511a287b38df4529699e3e46b39d44a1821793887323132cfa91884ca78385c8cbfc92335565052ee70cab357b99011d7961d84d8e1f425f9e5f04ab22afe5f06262308a9c3e1a4bbbbe668b1f62a871a8e1d6e577c104207ed467ae64b5d79eb92430d078f1d38cc781aa071c81c0e577f256376d1d9aaa4c0868dc470d84222440e07751a430955f1df368ec3c1fee29abe4d5bd3361c4e5258fd677fc923c7fb42c81b906bda4a4e0bdb0d678baa9b6b55a7dc452e84b4e1b49f767ca8a18b9396bbb0a44eca5f499f8bfe9414c39db6545e395c60fd771ebfb92abac54928395354886f2b296d8bc3986eb9ce533a4e8d6a919fae1fa5c94a56b438b6e613453ff4f72a380bb35b8a8db62ed9448c1ab2385652316931b9d2ac3c3378a8118bb3a66c3899d568620cc3e2f45acae54c8aada1c62b4e1d974e34cd98a77576c5c9cf454d6ecc6f27485b714a1115bd1baa4d5a931527494cf5cd922c85b6b78ab3765d5493da44ffb6557112d10d6271762a4e3542d7bf585d089341c5f1bd2ba9bd3d4f711e1d27c57f93ae349be298b43689184b16f2528a8316b17a5b626bf209298eed9542091f6b2aad328a63cf7565566acbb28be23c23abab52a8b87672a138e9ca3cb62699946283e2a092ed7857be20d5b554438d4f1cfc779497925df4c5ac278eb932cf4533954e9cb4a9d8cb5227c5ab911327311643aa7f5d74f10c3536711ecff0fa753add52d00e3534714ae17d318c29512d2f1367b5d26afabe7f49d498385e7987d61a533d11bac451f467acee339638c66fae11a5a2a25eb61267b7798b53265f77062971caaa29cf4d7e39d4ee46307263c4e25063125f68f0133f65b424895499e6a6a5d4a68944c144c69e5862af90702fe34f6354f0d22314d1b33198d2cb961dc15e768fd498d2c61bc1dcc9f9fd25260525cc0883f03b41938b92479b8bb85e6497186d3a6c5314f177494a949b0a258a30118d664a1ba67b5347c4e6bd765aa25d12b3c9219a53a16e27d5e7c5ce10b8bbaca618c4870a9110e2944e096152729fc5d1175b71ed95c124c83228d418c4d144e7a88dfe7af7690e353b411c5dbe52f85dd222ca2a592310c760a229532989a64a6b23627c00004ca80188f39e2819637d9e43edecaec61f36252b6c5cbd24a82f879ea1630367b89fa18313e361461987a8e187833a35290591fd2b15f7e1ac2949928693e4fad4ae061fcefa27e436c5d7e6caeee19c75b967cc32f806385cc7eba9a10735caf9e5672591b1a3d18e3c1cbaea543ae925a830871a0e1e8c91ce0a8f1d3c787c69410d3c20c67bf56fcdb532ce30c306ff65680d6adce1bc26b3dd9ac62e134d8e0535ec70927d646c0942b4774f1ae8d841c6a2a0461d0eae1ac64b29316334391d4ea574b3f2eb6b9bb0ee5650630ec7301a5b4f52327238865e1395fd25fb56c24c48821a71387805f13ae1f459094b0e35381c44f8d649b3aca1bd11d478c34163e4c528fe9eb9b4e371f0d8e1868316f3ab64aef57e2539d478f0491835da7032f1dfb79a174b361d387470f760c34930e1f6625c12ddba24875a0aec4450630de77c13f75ae96bf249e6502b3d78f009cedeaf0235d470b459b9bbf8a7e178ee56d716a22c0821341c93aa3f494ec81883109600258906a1c619daf21272c1f45586dc0ce759d3cca05d214c636538e8b324dd82ef9952fa1a64282db3862513f70a9a56ed5e72ca68dbe4e3a5313063f209f799b228d410c3b9a48afe2394a91ef10fa146188ea2542d75c92efe277582304612ab018693555e6e5082ee841a5f386b0537398d25fb6cda0bc7ef0fb10aa649d90675e1184d98865cd35b1a365c3868f41016f4ab05d5cc164ebe6df1527eaa85536fc64a154f6949d9ccc2e95783f03f595b928d85a3c926354afc4cba165ee12466859284346562d556407d7c56f66ead46150ebf1b4d3ec42c472651e1b8979fb326f23edc3485e39910597935cdec95a470d09757612e57d07dea281cfbdd648a4cf2063142503809d718e4e284d5d7fc84e3e6761367b3224266279c2d7d5775e94b35df34e12486cf28f282091b6bc38453f6e54abfdc113dcd124e71cec299942be19433954c9292e92e559b84538cafa9a046f5a80909876d7d2ba9fbf19e1fe1ec614109fb0fd5f9a9114efa164fdc6638f516e1e05fc2d676a3491f27110efa84cf3e21cf369dca10ce9b7b6f374a544308c7987e27c8dc5e30ce72299f5c450d18276922b7c412c3bad4bf3867b092adc41fa515f2c541ef5a8e8b6a96096b7b71facd76b9f26473703a68f0e230af29ebfbb66e4949bb38c5bedc1a44e80fed27ba3899ec7a97cfbaa46e2e1707d7de3051354d3a29868bd397ca501666d2ce986e71589327e929253e94c96c71d4b72433dd4e55b28b8e1d64dcb0614393c434a692ec680c0d5a9cfc4e339c147306ad54f280c62ccea1356df6e7a89d2e8bc3a6e85ba9de172f8762712cf523b48b578a278861712a9919d684f75578fa8ab3c677a81346251dbf2b8ef77fd69974c95a99ad38e59b1e5319c40db0e2bc56a5f7ab6bc3fb9843ed064fc6dfe0c9383778329e69ace25492a86ecfec719296063c580c1b36b6f44062201aaa38c5384aeb9c20de4b4a4ac571a369ab387a4cccf8a3e29841558a164b68bcf64f71caf55e92e45972e573531c45d6dba658d2a538598d9ee5eb937b5c448a9309174d69edd3af9c47713851359a0e59829a0d8be2249c6097c4922f6932371427db2e7172ddab68aea0388f90c975734fced2f313272d15a6e5b29eccac78e294157754598a79494d3a71cad94c593b4e9ce2527783e88c1267dbc429f709a7265d9894cb72a869d1c4d13f557ff40495eab44c9c6aeec466c4eb8e8989936cea4a9428f2eb2c9738a8d9fab5933bef6295c124380dd0b0c4f1cf92b039e396382586870aaa12a75ecfd3306f2a52f3342871141b7d9b41335ce3e8061a933856f5c68d1b54fa7b5112a70a6bc2f264bf306592432d2b402312e73e6949b5ef45bae939d470b87a8006244e5232990493151d63b019cfa32c03341e7110573b49fdefc34575c451c4c29f786bd28873c59f90b373a7e4bd60408311e7af53a73195694d52c8288d45a48d55c4a6a14932d1355b34336824e2a484cfd5fbf6c5383e220e42c3e5edd0162e8af810e759dd947d935a7d439c6413ff0d6552bf62b210a7af13e395c55c1671428893904be2f4f625d9c71bc4e97574852b88f36651f11f0b75e5178d409cc24962e4fb469f3a478f1c94616800e26823c24f2ca5a3f187735feccbb253f2c33196744a4c7294d02d691fce2676dcbd85daa094c9876357ac1495614b4eadec212f255d58903145430f27b9e43aa67f631e7b16021b36d6b50934f2703e394e4927b65e2c65c2c3d93668d0f3cdf01f3f8d3b1cf356a692ff42c7afff061a7638d65ecd65bee7584c85a0043a705481461dce7b6a5ab93549532a87c30cbe018f1d698dc7f7488a061d0efbd7194fca24062b4973389be6b9aabea7e9e87238e653e94228494a1cceb91e6a2c87c56ca8e17030992dc92af51bce25e77b49d6febdc96e389c59c6937c4d5e8b4d1b4e3ab3974c4ad0d2312f1bce1aab64f779cd69c15dc3b9324f4925d4e7eeae6a380699f65296b8a4e1d8b39af444d13193a2e1a0625062977b79865349a32d29c1c34bab34c349455cca707e13351744fdc8ac15329c73453ed4d6449c8e6338da492b42ec15c3415c2853a1e66cb74cc270f8bb2b17134d0ae21d188ea54789766810194a5f385bbce8a6a4c7d3375e3877a89592ef5a174ea7b349aa142e1c4d123eeb6f4a74e42d9c64ada98ecdd422dcd2c239eca2c7fbb75938859269625d305dfb12c5c24978ef92456e8c49e84be30ac714ce56afb2b689a16185634e4b2a89f1abde279f430dc70e98078e1d66e818a3ee0534aa70dabccd9d952d6bb623c3a04185b3db9fdafe028971a8b42ce6a2c27140180a88c2c160200840e14c2803150000001018918502814898a7d9f0071480034634223a2a241c1e1a1018140e0e161006c2a030181c06028261301810088382c4e1503414993f1ebde4a53505c7ead142a0c4647d0babd47b618942897298b86a205689021a6f354f098118e7fb7c6c1b6661aa2ba7eabf0d209d69da1236a30fbaf68f1b4da7bd437cd9185843c52c2464f4b4758bda457109c98b18c7d66153a7728ecf78d912b6b903bd0b4f7119cbb8e78afb2f5b31d0314e98202f198cc64ba0302fe361aac32c4ede942a153b4188a0b02c85059e54f332b37b0188f1daf9ef283c7c687f1e0789f378c7c381d8439105f3f43c8291c756da5fb5fb6661004772c414e6be38647c7a533cbe7a36e7294b2144b5f10d7373a2df916795430cb81c7f3e81aca5d6e6c405372c3457945c0e15e54077a198bd37092262d34f1df08868d8971049deffd29b79c8d521831d100fd8a495a193d88f3aeb9c137257a0615f48466c865becb85d22f59527bf9cae5b5ab879592a01194642d744e0476e1aa691e94abcd174df7eb8c7accb012e9a4cf8ef0147c3a51b75bbf1f31e8896b79176c3e803834b3608165d62f098eb6b875585b4380bed78fb90f9cdc7c1962c1270d958d0d5175b139109fc59f84a884cf2b2cb12f7cd87721d4395e5df52213406a76cdc21ca52096103b2deec4c18c9e4bfa8f349e73f035748fe8df4a9c96b9aa2c284eb56cf699090b5285480a53096eca0b59c2a364db508b4d8d22357a16da5e024b974010b28e6d32d3e165a1978ce06776d92a2344eeeb6d045fb493c2b33084126bdf1e67bf384f06cf1073b80cad9f2fc277672c95643b072f101b12a29f373839226e9129f96dba0210c137b6a9009d8985d747d4e128888bf06fa4e87a64baafa72bff310aeb2fd7d4545be2c2ca0f8e5426c4515b1a0ff9149f597924ae5c3caa723585d68a13f0719d7c567add606aad90d13f387206204aa9226dd58725f88b2733d9a9e12493d1acb1861fcdfc8af9d8ba0e30219da1c52f3387c85503262a8ecc64e48c1923ae7cedf30b63c1496518dffec66c9a22d70026b01590dd85748b1fb1f929e8b0fd26dab5e20656b30171bc9ff1baf511c121656986f1257aa247b2d093d8d8f2a26ae46014325c0eea5d7f54eaab7bcf606fb051bed4059a20d451416387970e0a329a62ed53652495486eb8b0d32e359a6db6d7c06471adb867bc69fe2e0e0e178354e33c58bc5a6a4dfde9a27f9d336b05ae81825de2c2692c7b49a33ddb99f2d8f7f93eddf3f5b2d658d90c1cfcfb29f4cd3ab3dd83a7769c7c14c3c8cbbc415a986b4a5dc06c6650301d05c3ba080f4846402cd20e967cab4313184e84df20c5109638f9cf06532d3682c0854c337d0300514018ee6143632567d960b6ad0e6f74d83659306285b28842a81d4938200e8656792a0b8a3d8827398496b9e5a43b8147f23c7580fa830c99af7d7b0f6a686c5244c137cb5043f34b7b62a863a50c98fc058ecd64693a4a3c95c106670cf27bd62c003479998f24287c5eee4e4e23b9649d7d2ba442208c5f850007c23331b450508bdc8853df208517934f2224a12cd5e89468e8026f6636aac3708941a65a3b92c107e6aec48faa6b208909e5af5ebdfab9c53f54ae8b1fe416dd86ad3aea62d693162de006eee01860d0a7a6db14d4629f5f1aa2604506e4d2401695af4635710f359be0b80c846865833dc3f12ebca5ad77cfb28aa45be4924e6b338791fecb96ea7d25d4e9c3919331947d0fe3f70d01408668c7ed26208d24f21314e643e2de4ab15d33c4654d9542b7d450e2df8e97859c4085351739dee293f82aab49b381d43bcac001d5d12348882ac7abc563da53e036d0c71f4a751deac293237340cfa4860c57954a751ea114ffefab1cbce7219211e3955f5896283824e914b2182c604e4cf34d116bd5734a035a7d7b69fb579de1d09aec7f2f53eb82f2a93c2539351b18a9475c154ca627819001cdaf7c6b6f4cb3dc3e069cbcc81d8f7ec36fa1ac498d9fc707e76cf01e5ca70e69615325035fb942503df0f49d37692af1b7c7fbecc07a28007cff916bc7beaa53c18d3db8886286c779eaf97d12bf030f048e4b7b0b7bea9db6f57001784144e43def97e8fe0a97fa9dc64819ebdef9e1cea5d42ac0dd22ff0b247e1fb5698db8307a48ccd4ee0f31694bdbc517763b281870df2fdd5f14f26d9f8c74872b946548437e359d58525d4bed6aa58547bbcbec9e8070ea778d13ce4e763aeb4f948cb9b4524eaca630281c9415d9b6d38471ea5b2e7cfcb06c4afe17e1f390424ca177709044f7d891eb235f030597244c66321305b4da6316cc187bbaaaade69d5e27ea8f4becc4fe813c0b73fc8683cc9b232707f8f320c3c8c3f449d624bb720b49b68fc0b9e5edf98f54bc2e8ad331a54e06cd57ff68dc3b8711df766cd71ba74c5f169c30d37ec7f408e3b8bbcf24af303c18c1cc87cc604cbdd036409b2dca7de59147c542cc411437852d5f9cf7347cb65a94403a39cfadc519b21c8fbef7a9ac1fb06a653df53cc825d69ea7f5a144c6bb9f5f833b2aeb6a87a0179ded48271aca7395f8a36effde814e95e6915aede001ef9d729daeb0ab82170a776cb22925b3c0702d6a0f64b7b69c63aa6dd9174d687475ddf808fed77e8a03ec45ec6f2dce224dd152dd4dd73ecd82748010a8f49fdcfd1907719197e739ac6bf775809b849b7106f02b74ffce9d7a76dfef8e1ce4e99b2821bbd4574b0953a7ad5dec0a1e8a385580ed22f5e073e51ce28201d692dee741710358f15c2ea66aee58a0a5717a903db18098d8c46651fed0e9568c160395a815da0313d4a44f232da01dc887f700aa8ee906167e71755f7eba0dedfc288a63aa6280104043fafe85020e63e10aa99132527490da6ca07c855ab11e5f1da17fda600c0330e44292a5c44cd88fe4f99e82b171fa41711efabda739b701e9571656ee3e57709a4a8d18cb5664da867d4633eaefdf47c6a94b6ce95276746307d62cc8e075bc40f60293f65e8acd5381946ca5ae1b5fbbcae73224dc2e0bf273c7e9c79306f90043160a9e1a907ad426c52fa5ac8243a9d33027024e7e37133a5b4e134de0d7c2c2c550974d7f8ce1644384e6ce8b8dd7722719a913fdceabc2e2b3f21bd004cccc5ad83a1a7a00f3eae57ddf9f6cbc94d87b96956e6666a971bc32655037c2bd2032ef860f492c0be2ee7814dbe01f790950e8114a52009bfbf698980022c3c122144142214e5e8d08da832cc20d684fd62f381c2f2681aa21fed47a8b6bdad75ae920aae46ad9791e65c80abdc5de36fd637e4945ec0d929fee5bace9a92426227ac441ed59088cb2ee6a2f3a4e7ceb48c989103192ea758f329b7645979014a0e511d39d57fe9f5db1b7c6b99ac1c1c230fde4c63e430cd9a7fdeaeb57cad9cc060861949dc08c6743e1b9ea1e97b2a7709e4961de02e1bba4997cd5b04ea6695f94e8ea88841abe4b852727acf5d10fb120300b8e3b0c51d89715481e60beb579f94bfed712eb65f6c9478df55371494a3ecccc440d895a8d31fdbf1e76ed5a9055591b1ee81e261812a1b054986010b17a6011d92ddb66a511b2fd578892cbe9285435756c3bc907ec6c345178e2e9e92b40221e3d3419af4e0fcff0939e14cc66335d31e65020a3f7aa778d13e01aa17e2cf4b7c396a38412b70432e8561edef3d5740fd62fb2960f3451218f93c24b0e2819c3e772327ec9b779d85e5d802502936fe6f1b59c2d397d9df50fe05664bef8a5dff03e9c5b8f213a0eb7f0360e18b7cccfd16fd8bff08c4655de8589f02fa5ccff0ca68d8991f6819c960ba9d4ca3e724c66485d9e54a4759572cf9247485c0c174ad88b9ae53a65e6ec1960023e2e565993a48e70d6b63d5ce53156f4fda522d7e274acce7161d38b1b9997d15cbb371014cebc0a3917a5f335b7c1ea8af0c4f4e532fc55c02d5ab469fb417c0056d34f3fd1db9220fe967697fa3d8d3e84de26cd8eca31b2821f5ebb9272af7de9595493fb78fdf308026dcbec6c0887201700033b738f14ea5a179ff410c27197bf91a0fc21e5254f2e4a3058ed903376950155ff47c4f6471ca6744e15ea9d858e17d6093b17f5dd14fb5459c870e8cbb9d50510beed9a082dd4766101d004ae98738cd678b4474c087f42a1751f3e97573be4254303df572e97cd47c142b4e19c58cf4b1e9d9a5d2523a04c2d015c36b726accef04aa4c81e05a5a2fc33a5a468defb7084f237d840c6e9bf116ff7b9b9c4d2dac25436b31e5993a2c7e1004c05755d929e647706b081cf8c86311ed18d60229a4f6b2b6eb2679be0a69f373366b43a7b4e034cde20c4bdf2ea6d21312b7fa9f2dd06268c8e715fc2cfd1ffc40988a85120ded9aff130670df4185b82e82480d39cdbc4830348680d512980ff0e631986afba16d62ff71d5b17413d4378b90dca63551c8370483f2e174a5b4bb2433c3bc7999a094c7240062eb25aa3301214f5d4660cefeceb083aac76bcf564192565a7192405dc0e5190a14f43a64a6c89859895440266498ac72b4712d35ab6407178f577fd9a440933c10fc20a81f1156c3605300f15c9155aa4933887461a09509bb8f3a27acf82f04cf7b5f6f484f326908882dd9be1dbcc3a26307c6e1ddede719214aedf95752b2a762fd650f3a8d24a2205bc2e26de29ec0f7372fe078988088d482b9cd045d508d1a2beae581a91811038e21e0029442c26e4ef58392c7fb67b256bda95d4ac979563c52566d69a95f89551955919fddbad326faff99b4586be597219217caaa19c7a594589b91751be833a1c78d850de1e3aa37b2ca86d272ef7c0957a9b05f63ef24890d71586f0a476dff6d748700c17ab424c04c81b05a54e13aef5b5f8725bad4abd4cdd37bb83055a89f038c0535cfdce4eba5d6cc36816e5beeb79e4a8935e0121fb08f1f8040f24a8a1826732e54c268725e410271bebb9412861a86cfc1d957023f3f0920cab1bf96fd0f5d7871571516160a365971da8fbdadf53a031661d80fa79d2aec0e808997e3bd8a07402d18244d16c58872597dced94d28bd28432253220477128b10390343173b0194bc100ff058c02e6009ccd0323bbe8a612ec9f5eb6207035af1cdd063e2d06adebe4cef2a61977d9eb4b4eac6ebd3c91aedd9fb1d35bdc18c0e6fba5a6b6345bc2362501d323e641d573ffbcf56ec8cf16ff6ad3fe7686ec717ace5cde46280807fbce1b239d7b00ea69019984bb1df0f261b335716ad4cd1816747849f05d970e296bfd4d11a10439822c808e408343201e00422504d11e808218210611a0227056f71ae5db32c6b3617f4548866fefdf3cee05eaee94fd9b6bcee7c0d9605703a56f90c168e2282d5c193450cdd5c5b3bbcd798502b1ced5d1bf63d31eab61b772eb5dd780fc370670bc28af9ceafa1f49a8b8ba52d2d9ea1f8b788e83745f5afb7b6225abfc6ae7a890811736270a43feed8498f5728a6fdc062d533a28bb4644385454ca73e9b1bd8d671b8846233734d12c1c680e86cbcdb7a1245400abc0ed978ebb88b68699517324837c5a5493b155ff96c92143093841caea8e356032713e33b08ca7c3e5e45e85c82a7f382e5ef01b58aee721e3de6c50bac363d90cb0ab614afe11e1e083ad736cd442455e9aade73ced58fba296a1df9aec8cacd0b72061e85ce9a5975f8d5b87ed11169afb7f096ddc1b2e14697cca9b30997f1b7fe84afb5406da0760834f3084a83e61a5c576b26e9bb3668673008f17409ed63b8f48ad3f31315e3b925f3b6419f817c89273f57ef6dd09deffb786de82efdc8ee51d15946eba6e2e4ce5d62eb0deece679e4611d194fdee1c6bd67356e0b9382b0a27707b6f63385c94afb9f3d9d3ad124fe6771457229330b0daba68f1c4e1f2aa9a3249b6d3883dc62ec6b4dda6e6b8f3aa17e86bb9b0d7d088d3dbc03cd1c2f7bb9de0bd6bd2de1c16e7279a6e9d035dffae69423dc813bf37771a6ff00ffb5fec99ec49cf4a766c555cb82e549bbb9f7475d0bee81acee402f05686275c06bef2e37718736f80e0c72a4a278d9bb661f5d34c068350a35629171ae5a21f38116a2fdb0b1a77c4528492c221751c577b925d046af5b6cb80a3e70d063d255ea56b975ab9613f7014aa582b6a4dc755625da8f19549c686096e34492a3ad18e4ab329b75290a2295d9623d828df40e5abb9b4fb16bc463418fb0b6bb8b69fc9e921ad8032492633e2f43c3b3f29fd3a5966432bc83a8045626f8c6f4996d5831a78564f01713cb30e1d212f90941fa961d588df9453b7fa6de93daba0ecacca0f0328bb7a7d6e4baa1036465aa3856824e584a2a26ed40cef12d6d4f879bd6ceb045425361491d8419123f8d609f3a0188a2fb28043bc35e38997476c70ae8f63376cfbabcf1225c72d14b918b6adf9f4503e65ee204a233fc1f325c11cecc16d29a940f230520fa90ee005cf3f980e8d1a94d3e986357ec85f14564b41c35e5732e306ce2a518b7fdcdc5748cd51a676245443391bfc6edbf73b87e348a9e572ca7cb348ac38da80201af44c3a85ced6d0a2ffd58608181153da6b8769822613d3c0ed909c36701dfa321ad184d120680c1a038756791aacc01b4d87a59d8c099b89049a8576464bd16895928c19155f8d33d34efe6b50b6271a19ed45638a6995af4e309583d677a5e5b253e2b98e7642bba211687934f1a0313d451fc62e34e67ba7ddc912b93f8dc94070779533cb4e636a70068a6fa59dec56487227340b4d8056219a29c2036d90112d8c46a079a3556866b4205a8366466ba325541a131ad408df318de9acf1142b8f06ffe49217031a0eda8c06a3b5281ad3d78350ffd965324ab771e5fccf443c89af5b9dfab4fd0699d97e5a74b301e6c908217efe43451b0a943a0770e3a30cc5450b0dcf1ba97295779f69567db9002546c3c487f0f954e31e47218bb7285c5aa35a86408c2242930dd4362955ec6c231e995d7cdd7f0d844428acb4d238f3c51d2c0cb177ef15c4dd88464854d3a35119fe5bd010f2c177ab1c13ebb3d6e732196af107ef5e687366bb20ccfc2e0800b91249e9b08e61632d362d7c5df8ffb7376f6bd889c56c0a10f4d4ef70726c93022ccc12f8af9ee3003e6642befb466cfc3be0bedfceb498f4bb3380fb99110a08c2df07dbbd82f8dacff801701d936c3e8a1355de329397c58422b2551a302ca8f1933679e2c989e5ca0e09fa2e6559c2941e673b9f0a41152492b41c01532f794ba003f479adf6ced923ca121ce0ca46d8cb83fd394a79fccebdbb4cb52c7241d94f67008f1251df08f4b6080b5e4688d8c1e05c14a02c85c45b9bcd9945b07ab4566039fb3caaf9955dfdb9e80572c7c9b653b5e3b743902906089a32430108aee18a2ef98fc4d7b86d9690e97def710b076079aabba8293f3e2dcf921fa445af0bf3d6ca7699d2c4ca17d9fc992b69b095b754eac5c939f8e3532ce58f9c0e21edca2f944189b1bfc712da274ead8e5d11b8a2718e9310cc517c427259a2181afdeb32c1317916c88b6abbe955a8f4b7d22e111b0d85fa1b4c7d55bc18f59dd77681332ef6acc524516797e806e412d5cce61eb080d37bfaae03f76aa8c12e3711e8c6ee5ef3c79bb99313ebf8175255aca2581001bc171b22af357751186521c5c30e9ef8953e089885e86dc73fe859c39e475bdc2784613c9afe2119cfdf8aae390b494a6106ec5dfd13ecdc7f79588180d3a8c49addc26d422d68d6221eca2a0a7e6f24ec4d27850cfe2481099f85fa4c3830281829b882ed0babcbb52d5cb0d73b6b1f8cd78092baa776ed66eb8c9fab66cd6205f5d96a21bc7335143956c70b6ad87fb99bfb203267d5571c283e18022ef4aaff0c8931a082524e1b2bcd492470e36956e9b1e118f6083a8b05fcb31c802ff12b9db9f6de5fc2e93383be5f0b3feec920139891253482fa847cf9adc1612da8e61e06b30aeea03dd9339ee22dc0ddfdad9d914f36ffad40b0983e39319d440eb7c24e49a58029b447696c1979d60154e261242587f9b8801f51b5a67273cf3418b3d8ef8ceb2cb314555a8e20d2dcdc87a989573223f244d64d5c09a995a06b828bb87f4aeca65ecc378a71dd13eb8c6d3969ea4851b833e300f30c40e9c4749743511b2fe891b87f075cf148c93d84286fad6906d76f977b0fc8de586625054877014be93117b0ca011dd7e523a3d0cc30aa338f335d86b425a2f4a15094136218c16da67a232c705b3c99f6b1e60bd0e27163d9d3f69c41036f83ff813da6f70ed34b432277b65f8ba0107af317ccbc237a624ad8e07486975454d0d85f9537a26f0d4c814606d7ab29c5f2226ce00a780263b71f9bcdc21fec9d48fc62272fabef7897646f662fc4cb1abbf5dd8df9f8596ffd4676357fefadd73757de79bbaf316da757ea52759fba9dee4a3741eaa609c73cb269bc05ddef5d14bd8dbd84bd086f80ae6717446f23ee3d8fbb5fd55daf60efad9f98b7b172e1dc7aee961b9e7b7387b989dbee90cf87dd42eb5de645ed8aecadec9decd5e40df9d6ec9af706efadf71aef6de60ded2ae9cdec8d787d7483ffa6f7aaf706ef8df70af306e9befc9aecede48dfcb57823e36edd32d05e2c7b0779295dbf6e92dd66af88b7a31bea4adc4deb8deef5346fb6e3e576a1626f74ef3edefa2ee314bc0f2bfdc5610e020ff2fc9de33f0247bd638c15430e44322bf7e2da02433f8fec3b12839b8a1bf1ac4c5a41bd1c506df7f534c3966203502210068263a2c12c5f3004de80dd0a841bb50691e0eec333cb4eec0b650ef4e61944ea63b5549157800f9a873e6188a308429ba48267666031247f4851aad23d48966a47482c0e9f0d710b890a9cc1d900b8356f6e70c3068741d160c68621a2075321dd02a86e20988635a80dee064183c2e06cd06e900cd60d5283d0e0671032481a0c07837dc7b4c9181b301e042510115ae4c14c04ac84627e1088099bd4c2e0ae061b33acfb8b9ec168fc2688e819bedb56eb69d80cc6f42421852d036c20625061069377ee62f530501c744b508db02a0cf860c30c01093219e5fb0083a5bb3020ed77850ec6f8efd26e4010357858c84084cdbed155bf3018d3a5436f1ac00f96240443981120f0872b047e071bdc60edfa0a27cc086f040e4b905bc2e66c82c4cc08909b51de39d3edccf87c834db294fc0202b6206cd0dcfe28d5bd2590d1893ddef3bb0680016006605200e157e1df5702e003d427c0bf621ae15f028c01540018801880368016e8072001b80009006a1a706560e4a2ae003c400780620404d6852c7b0828027e05d006080518b800b3f629d3c9097065d1adbf6f0070802080a203e0213e7047600994013e00e4015503d0034881228009002c2025400180001a0098013006520108000a1000204e00e1f973b5771b2017a8d76220cd188420bfa8f26f881798a96b3dca3d6c8f92c7488f131bc022438848beee69fb7f06be4f21c29b997152d49996b017c447237a38ad276f4ae8ca2fefc806fca802becb84690c3660b0a67230333333333333333333d3acf67b8bb7f6182da4dc5b3299593ba42a333333538866a7f34e1bfe52bffd30e305efe09da1947b0c950cc70b7cd56a8cd794df809cf31f8ae7f6e16c6f8c0d085d9a16d7d35303b27cf4ec34625321431a3420ff5c575cfd918757b320eb59f864257dcc9005d92faaf451ce77c9b7cc214643806a0b336241b456cff8b7901eb57b182ee0000e2a58906bcb4266f7e80af774c1010a68a0500d335e41ccaed85cb7c1423fb57305a1366676913e8a99ee8f14981d14206c05b1d3ddcb531f851584faf21c71275bf2b119ab60fbc825cc0f6623fdf8f8b83be6c9bc4a397f19d00503128d304315a40c79f98fdfa3e7c3b652419eb8f01cd5b3b53f8f0af2618a99965ef6c2d7334e41de53bbe87a1bc3dc360521737c674e359582e4f9684bd4a472857d48413ef0e3a8316a96a32044885e7a8a868fce872808ba6f3e79d7e2993320cc0805e1f27ed4ca3186e730f10c509093fb6f0a5baa8e316ec627481a15659d731f7e30558c1d18c8d1c38b0fa427c81f63be94050d625fe904593a2ae604614793a69d4a61dfe72688f1d39f82a7bafd669a20b99ba518a6cf0471abfb28a57c1946fa204c10ed35caea32ecedf77109d2c5ba9bc5f0c312a43e8cb1681bdbbf36a612e4b7bf0eaf3edee0332508d271295fac0bf9e52488ab1f978ffda862de54910471c5fad08f2fa891208964b8110fd235ab18332041e8e33893f5967f04594a3cf58f624a61657104c99298c7ef863c4fe1462c91d15b63efe550c2a1e371802146b300c78e1e66ce432d20021c0b98c10818cc5804f9305bec34c576d7ba9c198ad01989281ffa86496a6af26620c298323c7498a6db9783eb1006986108f287fd78a59fc3749ead78f40863462148e6513ac65fecec3f9e6f04330861660c8294a377be14bfe9337e5c046608825859b3d36fd690e1358120cd68f63c9d0504a14c74e37ef654a3e51f8899d2fa0f4f77376ee80752e78a7e5829c69fd83e66f481dc7ed08759639c66f08120565d39e5c75b7e78e650227a8103745c00f530630f2a98a10752df6d3eaa76f31cbda644ccc803398c5db8cb9a36c6e7b380087004800e33f040fab6d3a8553994b9964375ec20222222224d3c2bfa60b380087004a00c33eec01f6f65cdfb29533994d4bf3811d07181d340ccb003492c1f5fee83fed4f5f9b8aa03a18fe365b414cd37f97328f10c3a10d5c7b5ef365ea5182431cc9803f92864c894924ce790dbcf9003f983bf1f9b46939cef830433e24088ad1872692e9f0f1dcc8003a1fbb2a7380b96635cb198f106f2ab1f58faf1f1617d8cdf0da4d8ca47313bac196d20f551fe4a35998cbb1a3690ffa0d396acbbce46bc06c2c70f3f7e58f938ec573590e3f2b23f7936795fd340ea6cdfabcccab0b8a281a095396e5b4c9e81506661eaa3eb3da5ce0ca4c8b9da0eded9a34c6520ffd1783e0e329a6efbf82003992e8eb8a6e8877f8d81a05d1bf37cd0c4401ed5ade4614cdc30902774476ecd533efe63c140d02cff47e57f5c1f57fc02d15bbf8f0fbbbbd536ccf00229d4ec72cab23963f51186195d20f85bb87b67a5661fea85195c206d5d8fbf9ec63df3c60dbdc28c2d105a633eca48cf9dfeda0ab4404e3f96fa30fae05ad6ce0221e22a7ff843bf871714d837ccc0024936f7ec6ea5b688ce8d1b6b98710582546e99adf2e4aec9ca33ccb002e953e7d98f19b3d665ab402a0fa9defef92868cca8403aad7c7ca4295619664c811c2277a60f8389de4f378619522007cd77759f4cb48ff595c28c281034ffb168f63f92cbb569c20c2810234b63ca18ce24cc780239a9f5e1e575fbd4122d126638817ca136464ff9ed8388ac30a309e44c9634a86fbfa6dd308168213c279554bb9c5b022946edfbe8c3c949550433944050bd349af36ae5e03149205e6587ca9c73d218eb0ccc400231336f0e9df43fb8a739948c48d11e8164ea3367e73f96638e11c887e9fe38baafd3b72f4520f7d1e69dedd13ef2b48940b6f88ca177637a0a6f08a4b9d8cc8721a319422026b3986fcb8f8fe35e4a185d81c31f2681194120e5a3749be6615d42a72fcc0002516475b3796a9093cd0f4819ffd8d6fc2beb55340a337c40cc4a9bec3d2f337a40283373d9bb2c37d1cee001612afd514cc54b154d9eb10372c86eea833a0d7dd3ccd00139fde570b6c1bc8f2722ccc80171263f8c0362ceda7efb62152afc19372076cd8a747a12d9f74030c30644a95ff1e3988fd23ac3336a400c4b6bdd31853ea8385f60060d88166b36565b2da7d47328ed0964cc827cf831a3767cfc838ece0b5cb0e3e40b64c882201e3baea5d43b3fce552ceaa3043260418c29997792fef9e37465bc82bcfb239536f5d6d49943a96a05325cf1daeeac7cea7be8f015388f5610bc3b851bd5fa7c58e31764b082a069636b2b93e9e2cda18443479b5a0531b4ab45774fb9f21f840219aa205f50b1fbefec9410ab202315c4cdf9f5ad927625ef5141ac1ce5651a931f7cd04f41da1c569d2fa357bb630aa2fb416ebf751f7a87a514840f9d8b7ee4b9d92f2605d12d34dc99e756ce70a541c62848b56dd3471f2ac6fa15053947baafc79817efb650903a47d2d4dd2e4e5e0c388ee30a006490010a828cd494d85e3e3e940f4341c627086299f3fbd187103f3e8a27c8ebb3d9f5fe72d1bd4e14f3e975a6507d3c41062748aab1b39992f91ededd840c4d900f935a3eec3053930abd019b09421f9b57fe710a191ffd404c10467379ca6a61b38b770952c730a5ed471a4b908fe31f9567664e2588657e9ce25547eafa228312e4e8f16c8993bafc1983769c31c488808c49902dec631ed1985a2464820c499453b1cd44c397a8c357e038fc908c4810fe5df48fd25bdefc47a9146440c2d8b3cc5cf3fb58d03cbcc7186180b1c3606241c623889f34c2b3e3474f9a8b820c4790326c737cf81c41462388c1fd0fa2f251dc14df653042edd0703f0eb5a86a73ad9009cffac7c77d988f1c4a5a4890b108821f65121b1d334911e451c9fc414f533c6fcc202311e418f5eb438e8f0862a7fdeb30bd5933f887207c10cf9693889d6d6f0872ce87d6296beb7dd0974290a3bf4df37bae3c4d2104c1fde4f3b41d04f9d673738c9df938ff5041ec7916daa50682ec956cab5a3b54760041d4cf14ddcd7d3ca2dd1fc895fcf8a0d5f27ef02be5e9e3e9cbfa40b2143bad520a1f88a7eb331afa8f63fda83d900f45f3451ff9c7d698f440fa8b1a5da13f97a73ff240f0e4079fb68f829b031978206fecd807f79ba40fff38772065affac33ece0bbbe1710d64d8817c30766b2966cc8721e7185643905107f2c15bca12592e1d089bed4226fdc034e31f732064d874e39dba62432607f2819876be2b310ea45c9b7354aede3f66e1408ea81593ada419e30da4abed38918e1b489e31dbaa5dbe2b75da40ea7e4d0f7eb8970fea650369f434fc1f6df4cdcdad813cde95423ffe68735303a992a774dc10f651b23410d364f5b16ace47357da181d85e7f3119f6d19f30c2e81b3774c83803a9eb3cecac346620c86ebca8eb6396422c03f938e7fb303ae7ec5529642025ebe89b6576263ee20e648c817c707e9c331f3b68d62b06f2711f6b3eaab3ca395dc3408eafef8be5399239070329322bcae7ef6e4ccdc5c0a1811b37fce817c896fb388dabc78dafb8066edc303c568617e886096474c11afffdc36eefdacd600b647081a46fd6aeb75df1c7fd16f69453f7e3ce4820430b644da163b6bc2e9e0fe35920e5bcb23b1a25f3328f05b2d67496da1f1f1fc5f50a846f370ddf291b7358ad40da0c7e1ad3be6cd95405d2e78c41d2371f46fd1c1588ffc7d91b35f3d148cb32a640b68ce1be9a62bf1249812ca6331d958ff3c1a6b86f60a3401cfd2b978bf92852816f6065408118d6b937fb3f525d31c4706314104657804dc613483a9bf7a68fcbfb204495e1045294bd7b905dfb4bf1a58c2610d263ac427fce07f2930e16ecd091021d6330a11cbdca8f921f4c1f3b7a883186c92510e3d3357ac687d69cc9a184c3fc14ca50020b64246103eb021948b0c2367deae30dbdf90e1947f8428611d4ef14377aacd7c860c82802d92fa5b2db7cdc29a37717904184fc8f62e335fd5df3973104a37bc7e76873283940035d68c910c2e1827784a58e59c9a144dda31b00c68e1e3ac4f8220c30bcf8f8a8828c207c9c7fac29a987b1d807ca00023986d6f58cb98f2e77021d5ef0e821068e1d3d747841aa64fc80b8fdc9376e95e103821f6930d90d66f968f71e90362ce8d5ad4485e83c20e8458d864dd8dbe63b209a485e564ee13eb84707640ddac7f6a22936a54b0e081d2343b4fc38d78f6d1c90c362ff66a57d0392a7d0f1a39c8f93a7d50644f9ff983b1217b20fae01c90f2aa58a9e3b8d9f95410392581f6eac457116a490163ce74b2d9ecb9205697365e60af97339522c48d21b7ed81653452ef005801004000bf281c5efa785a694ca0f01bc829d7f994a2157647ff8a3e9abad72db0ae2c6aa48d9502da9af0b3c05d845f41feae7587d9c3402295d27f5d8397527b308e403ad8ca72b1281e8f52a968f3eae546e8740746bede33e4c492e762b0472b6558a4c1fadb46d10089a6b9307eb6381408c996739ceaaccfe01212a68d058f1724e1d1f90325432d1ed63bfadf48094d7377bc8a80e8007843e1ef53fdcf421ced401ec809846da8f421f4a1fdba803d001aab5a3f9f8381d400e48a18fa3eb25c916937400382089557de66cc96c3c03b801397775fa034fd992ac39001b10332d563e8c7d793b9903a80131de6a7778b1d40be600684096393d75ddaff82e67417e4b3957863fecdd58ca82942d87cbbceec71fab8c05697cfa486395b0201f5fca73f28729573e2a5f41ca16f94c77b3dc1857904e4ef42ccf63f24b2b083232e5e22aba95c20af2a6cbc7152e5afa93ab20cd9bee6ef9518ae143559023afffd83c870c19c25450b29e19ab6250418e392eae65e5a9509f82d4a9bba43c6b0a52380fed62b2be962d05792d472f1fbaa420ad1f87e73e08b5bd7214e499ea2ca669262c1405f10f676da6fc68fd0fbc50908fdddf72f575a020c69877e5abc1a2a5ee13e4cc616ce5f4b533749e205f72179733dbbcf9b84e107306ef1cdf3c4ffa9c201fac85be8d6713e437bd79ef705b39a30952ceca3479f938e4339920686cc73ffcf4777ec10421febdbbb246472c97205cbeab4e7f7ca0f5b625c8961b13125e25c88729c41fe71bddb89012c43eca315ad81d3faedc24c872317b9a7df803d724414c31917e107eec729a4582f0655d9a39192488f1b21f1f9407f7589d471073f2645379e30882dee774d5bc963da511a4f1edc3b3728b1184cc712a574aa7efc95a0431a7fc07f2fd9f6378a508b2a67839f8915689201f1f55b9584a153b6b8508528cdc6acfd387d5f32108319d536e673eb86cc910040f9a5297d6795e498520fea157beb4ec47be2111829cf36be99edbf3663408525bede5ec07912088b1e2313db69a9d4090ec0f62ef633a05b90141c814774a4e4235c87f2074f09eab3f8cd963e70752cff8d17b671f35fa4054912e977cc9aee9a30135f840fee383cb15b35de84c6b0f643f8c69db7a6b2c420d3d10d2d77a6afb30f63ebf79201fddc788b9a4a98f34a6061e48412ca42ea7669d8a35eeb0f961cca57cac75a4795a6fe6fcff3eaabe3e4c0d3b90b3c52d45869ef6cad550a30ec40aeb6fdf9cebabcf15420d3a90ce46fad862f28c56f61cc897aab2fa287fcab0cb81649ae67ca35ddcaa701c08d5d5f1874133e7ec210fc3051ce0e16184d13c3e80831a7020a7098b1a7f18d36b1fdc3cc88d1bcdc36bbc8198e2666b4ad938331e379023fec287878adbbadb40fae3e3f9084b56ef616303e93d8dc45df8036bac816ba8c16aa4817c79fdc8341f8c35d04054918fd25b7ed4bbd738034937c6ed5c2c7cf3c70ce4c34e2f599afa537ab9461988b1ca2ff371f0cd7d1c6795420d3290de47dcdf3d8e8515390c35c640084d9f0f3fede6e394a318082a6d5123d39ee7980c0339bc73757bec030c35c0403ef495ebe3a3b17c341afc02c9d26b9fdef981aee763bd40b68d7e20d6e80279c2bc633e0a7b9693e102c137a7fc3ca64fcdc5d8f185c1b1e3ec1835b640acd6130dd587f9b3677328a91608d65d2bbd52c9a1747434d60e6a64819c5bd1cefafa68d3e6b1403ecae8a12b8ac5caadf2e01ad4b802b97ed62d5c65b38a9c43096350c30aa4abfbad940ff3f181962787e25822ad0239de85b798f39435ca54206be7dda6eff87676f3136a4c816cff5ffd16a5921fc84ea82105e2e750a9a25d4c6f49a35da81105d2b55bf8f9d15020e7e6e3b4cc8d61dfde2710a36ecbd688faa537b950c30924cf7f1ce299324d208bdf455ff71415932599408e96e737dc3b99b95a6309e46acf19dff1513c2bc61807c7961a4a2067fe719e4dc6b8bb70d4480239868a1ff41f6ccafac90335904070394d4b91b9bc59e6501a839d50e30864d3cee1ab37865c08732869804731a18611c88716e25b3ab7fb29e606358a40cc8752d90f37ec400d229082f49ebb5dc62431e0c60d2a428d21d410022956c8b89e34f5dee85d3040878e0830a00b1c5d7080025d18eea14610085395c6c2591f6b695f0308a44de5417e327cd4196bfc80d89bb2c3dde5bcd13235cecd98d6236af680f49dedddfb50e501291f56daa092e647b75d8d1d90373ee63c579f82774707357240fc034db2dad1155ac31a382005fbe358bf9654c78f53e306c42096a2ef2aa61ec31a36201f65fdf161bece996386aa0b0e3c806ad48050f26262fd56c04841508306a4ec2a7f281fdffd008d5990f78f9274fc8a3994f4ac0768c882547dd817e2d4ffa27861c638ce4393402316448ddef8394f99370b7a4480083460414e3f1ef37cac2e8d57908ff2fe51fc3e9e3e4f570e251e9a011aae2074f751aef2f0479acdc7a163070e3e5fecc8f1018025d068c5e6397dcae8e8c08d1b3cfa8b314ee03c8c0020061aac207ea64bf2f974aec3caa1d4a37b8c51813c5fecc87116a0b10af29be714ef30f9d85105a145ed2bd65e1d17f0302ed00503ba0863078f062ce0c68db52ed04805f948e732563e8a9e4b4205d1d6bfb2c7183f94eb14e43cfa476d92bfb12ba5cc40c31484cbd3d3394e93a6d430814629885ef9f8f4e3857e65b5811e2e469182c628c8b9731f8e1f481c061aa220a7acc9821f44c66871434112c90b9ba3b52c7730021600c38b1b375c60be08030c2f8e400314e4a3d690588da97cf5f20952e8c8920d9b3d4148cd477f18fd72fe618a768298b27f9fcd1fc509721a499938f7c306343651a47bccb4561ffca1fd51d49c29faeed304f1fe289467ad4f16ca0f1a9920f5e6be99a88b041a9820c74bf67d185fe63f738640e31265c99a0b9d2e93342c410abbd155ff0f4db73260d0a80459cee70f4593a7afda655ed0a00431b6a289aca5fd81cc9320a77431ff7f0a650b6848a268448298734cffec9a4ada8704f1eb376e8ea12696398f2087ad14abe2fdf82fbaa80a1a8e201f6692dbe85d72e37af14518627860023dc6a8808e864051f9d9a1e30060041a8d20555477eb681ee91eeba1a34d7b60cb053c7ae8700ca3c1301c3418b1341651341461d6f6e695b3d157e0450f2fc0f0820706b0da402311e408cf8cfec378173dd34004e967efa36f7cf0e995c6210872e99d8f43eb86208ca7ada57c7c1cb3342e0461423346f44deefb8184209c478deffb25517b1044550d5b49237753880441f498da471275fec7fa078d40146c4f773dee0808426e3ed296fced0cb5fd8e1d3c30f0517f20f7911fc8c6b16097553f10e3ff71a8de493fb4b30fa47ca031e666fec3d1e8f94012a9f4b7f92bc91f65f640ccdc9b3ff2f0d4ffb51e085f29593e4d3fd13f360fb97dfa9472bde28150318c57be86fab16708a0710762b8fa87ccb21197bd1d0893eac7f952e563fb5ca90351e58fb32d68af474bd381f0b172faffe4399082e7ac28daa1932195c3972dee8f07d0880359f3c7d0dde818634cd380033e7faa3f7e38d17803b192a57cd8c739e60652688b958ed5269d521468b4212df7a8d1f92c0b051a6c2096dd6af5c7ac76da73bf40630de473cb153dcf6dcee90f2fd05003e1924a75f85c19970f9306520c4b1e2763fa91abbd5ba0810662aaccc7f5dd8795421f25a07106f2ef1fcdf995aad668a50d8f31766c80d40c79be4e69aea83aa05106c2678eb28b0f71ddc7dfb871e30619c8412a8515f5dbca591e03f9fb38e5b3643931903c75b536feb1c7fef5071a6120765fa6ccdd060d710103f1f2c9e9c4a6eb9cf1b334be407c4ff239ce7c778a190d2f9072eefa48343ab6e5bb041a5d2057ecfe5bdb9bc8bab940d06fcfe17cc552c5ac6581c61648b3972296331ffd7a5c0b44eb1df183305bd73f0c8d2c1047fec842e5b3bbea95061608fd7d9841cd2f2a069bc615885a71666341fc483249c30a8458fd4b9ebb8f804615b0b49f32d38375d0a00229864c62b5e71f11151c3892a6403e3eccde1f7a45cfd29243c9f50b1a5220aeec27d7145b1c4747e338de83c70e94018d2810ef2f3da64d198f3f325740030a444d8b59b3731fc469860f349e40befdba5052692f2a46c309c48d3217ffc787e982015d1c231cf4061a4d204a1fe7cf1afd07975ba2c104527bcce539a97228e5126828816825697d5f97ad2e18071a4920fd61eaeff9e38d06128823be9bfdf838642fc561801101c715ac81c611c8c7fe16bba40f3438d03002f930d66f9cecdcd0995f34d02802b1b6d24a73f836f9b30a408308c48d7b96821f75b6eece432079b8ed986b7ea839ce40a021045286f6a54dcd602f7f7c58814610c8d6f18ffcf2d6fd668a1b1d3b2c604fa00104f251d0a8f4dd2a9646067441bae00005ba2827d0f801296d880a65f938c4b8ea03426f6e1fd7e4e766c9cb40a3073ea0c10372f5b1e75b0db3f617a4b103b2f61f873a20cfaff9684a1fffda1c907674eaffe87cbb921f347040ea0335c4bc25c581fcf93ae3eb88a63eaae040f043d98ae1d2d21fe76f2087abf4e37cf4955f3a720349e54cd37287cc7f1b8897bc6d2f66f6bed9407cf1c33ee8b31b7d7b0d84ff90797f57032157efa54b4d0349faa48f8f0fc6bafdb08f2dd040cedb63612ac7cf29e6cf40baada4f97a376620c868b43ff40b6d510642bf95fc8597b72003f9e06f93a9881f8a65700c969bab6b59c769b81f4468fed9249adb2d670b3110fa4063cc4796aeca3a97638b3010ea2d55fae7e9631f9fc7166020af567c6c0ce2165f20a81f6b8e1708172e1f4a4dd7b4077b8b2e102e75b0f27c9c62caf95c206e0ae57a2bf387391fbf0592e78d7facc1d7024173fe4a68b43eac4b1688ffc707b6db079dc1462c10df43e595befc0a84f4cd199e0f752b90f3273bab7cd07e18f62a90b4e5fffe283ba6aaa940cad107957de27e6a9e29105e35e65be5e3c31ce391022957ec8a9e74e4b58f0231a5fd7c9f3b4daf8702492aa54b1fabba227c024956fbf8301f544e20ac885bc4461f88dc04a28cbffc1ffb619840ce3ecc071ef3f1619640363f0cda29fec184c62881a0f1839a06cd7c5c6d120895afa28ae78fa3211208a7f1fb0f7c83c779e50884c8983cc666773c6d04721fc6ca55d399b3c745208a695abffbcf8a3e9a08a47c24950f22a4d57f3304d269a67c651d5d230452d0b6c8d8c78721b61204e2666d8cfc2391931b08c4b6954b29fec13f20f7c45a686fe503624ad9dcd632f88146dc03a2f4edc718f48f2e58960764cb3095b3e9a3fbde1d102ce4fdc2ab03b27b99eda68f2a1ada1c10566eaa67bbfe23c60171e4721fee64e5ec77037214d7dcaab86e6103f29e89e745ad8e95748b1a905aa3bd35e6cd369bdc8206c43b933c0d1666299cb320c6654e75e3e9fbad940531d53ade9e95b1204e47da8fc8c6710f2c4895accf0fd52a9e5bf01564ff964d152ffda0a3e80a726e567a143fcc8a7e2b08f225bb1eca42756505c10f4c73f203bf94efbc5510f34ba528b36c3946a70a72efddb9e4a920fdca6fce192d5f6f0c15a4f8c976e7bc539062bced3f98d3ca749b8260f30795ea53e7775929c839f9278b1dfc283d64a420a7f6e3c3f98ac9ee37a320653f4cd1e3eb120551d5a672cc9472acb842410cba1fdf66152808ff5ed1d6738ed2399f20f451ce31a7fcf1fdf1613c41b28fe287e992f9515f3a410efab999f291e58f152748eb7dfc7512d1260866e1c71ff50f4d902b9a86ce775d29c49920a7da8b3bfd7d68810952e71fe9fe14ad21fec82368710992c5644a69bf32abdc9620de8e5bf0a38acdb1d3a54525c812b2a1a2adbdb7db3d2068410952e6d0980fdcb623ff305e7c81c3053d4eeff8820957501f9330c432e23ab28ffa3064fb38f3c15f8afd1c7b5cd24212648d3fb77f7c206b110952a65f8f9a1396f7a30524089f7e247b59b2168f20945a7ebe302931d13b827c50c18f74fe6364ff458b4610b7d2b47b58c67670b560047dd426ee5a12a215ae5997e28a4e79741fcb2e82b4a1dede6bc61468a108d24ff6e166daf04776a71689208d7ae5cd0ebd168820991f26d9fefdb53804c98f474a3dc58ba3fa1a827ce89b32517ef4376308b42804513e6b8cc9afb52004c1c65efffef8b2b4a6b418c4de561652dd61e9c7c7a18f7b5a35e8460b41f4f1e99ff9301e08624c7cce7bdadc9a0208722737e9aff5e34dc13f10f3a7f823cf7db771bd1f081ffef836f243e5e6e3fbc0b8668bc92fce07f26165967e1f48a6b1f81ec8c7876be9bed10349b64d67374b8cbf792054fa183ddf8ea718c50351a28f2c6474475ff40ec4e03f5ef39da32a3c7620771f6ff4f551aeeaefd4819c6b3266ced6a10331a6b26afa2cf2713a73204ba9bc65b90f4fa12307527e6aed3fb494f9f8a01307824a868c796e935c0c1c48a94c3a681fe7e3c30fbf815ca1f26c44760329f46e508da37f306f1b88595da272c8ed6d8d0d04fbee943d2c6dfa6c0d643facd807d9369dd7326a20fdf1711ff7e1671df7caa481501a7fac19460329e61fc59cea3e3acdcf40903f8e9d5e8b19887dd87f7fb99681541dd44de22403f10f2cc9e6fe1f860a96319056b2bc44eb4387b08881fc7b5bad391f250ca4ddf314f3b17f6feac1400edfcdf94463faa0f3054275ceac7cea05b2aae5f0b3b9fb39d705a2774a481f1f5f74d09a0b448df84ef57d0bc44ef1f0e3e38f16887af97d3375b240d08c29a87689564ac10239a5303afbc75e81786f9e3af6c75620c6bb4af13da657fab80aa4f0a933d94c754a29a60239568cf51448b2119bd77f37bc9702b1c3641fe45e963e7aa340caf6ef8a8c7fe829080592fd618c8e96d58fbcdb2790c4c7e43a2527906bbab5b3fec61e699b40dc989b4b3fee3ded6399400ee1c7f671fec0fbff25908f43346d4aaa4a201f5e4aebfaf171ba1faa49f8f8e23cd8dba64820e5c5cffa07b65729a547208d7628d7ae249f7f23102ea337fb61da9c2b5f04e26da7cd797f7ce4394d04821f56a8e59d39046269a5147f8befb94b211076a62ca5fc95eb0fca2090fc584efef2355aca470281a467b6fad9ff80381f9ee3dd8717abfb803c1a173e5c5895f8f480d8e5fa07d3e19fa3e101a15f5d73ef6fd4a5ec80ec9fbfa7957375ae03b2a5263f3e88ec2bb3734008abdbd4a262ff07e28018d73be53ef8b6a0de801c76f275615183763620f9f1cf460b196a5103a29486deb0d91bfc0fd48206440fa99697d1de3b1f380b72fa41a8e5cb9105396f4c397b3f53fd1b0b426acb54fecf8aa3c182acb9fe0fdeeaa22ef50ae2dbde96f8e11fc6ef0af244c7eb7cd4d3c7355d2b88b796f2eac77be17c56905e725eef5c05a9caaa75255705c93aec8fb2755241b6fb687fe21e372da820ea653e0efb87d30756390521d76c34af0fdfa3620a621a713fcae1fad03a2e05a9437908c953eb8f928258bb97a16c2d2fa7a32077f8fae3a3d3ec5993a2207bb8d8d3475a2848f90f3ac69c2fffe18d060a925bf4415794db749f7d82f81615e44b5f7e37f30439899b25b1d9decf8775a230f59e54efc33841acecc1f26a6e7f3b9b20ce743eca9e32ae868e2648b1b364b385f39c9b4c10cb0f564379c7f25cc104f9a8ac36eda51fa8dc7509f265cda6c1b604297b3e9d975b4a1fae0479274b2ae8bab48729413e4c92f9f864dfced549909387e7db32f33e4e4a8210ba395b1099fb832f12e429d16c7e34ab665541827051e634ed53fcf88f7a04e95d2aab522a19df11e4333ff21dbb1b41fc982ccdc41f77e8a31941a8eb9f0b112f82d051ff386e77cce95b45903ba67c33781241be4a9e73d5d9a68d2182f4a3296716890e41eafbc318822855393bdd4d2e5a0a4190cbd0e6b1b963cf8420f64d4c9c253f06300872f683c9f87e793f941f03100429ec66aeb4a16eff7dc86000812086991f59db7788e501413cb74cbd2ea329fe3f903d4dbbdbcfc7cec60fe44ea739d56288d6b70fe43cd16ad3f72ebff2815816b30593f9f65a7f0fa4768d7e59613d2c7a3d104af3452b1f7ffdfd7920cd549a0df10797eac203b98f63ea7ccbf30e847dd39a2cf5b0d087db81e8b98fe3d41fa60ee452b5b8d73aab984207d2c8ccfb86586f9fe740549d4d59df341fd52c0782f9f1b1aef57120a6d03f5c3fbe7ff3120e843e3ee96e89ed8fe31b48959fff263f8d78eeb881e85d79b9b4d306e255dae815990de4c3d3547e58e3c79ddc3590c37fa79ce1aa81246d15358ed44b9e067292fb3e162d1f97120d0489ef23ed639bcd11cf40acf99fdff8ec395cc60ce4d8cae8fc9632104cbc3325e99081d0dfbe259272da9e3190f5b26773df18a22a6220f49f7dc7cd8f4d6118c8395ecab7df313385050ce48db26c67d2adef952f90bec332cb7cfaa5ab7881f879836db4fcfb35952e902f88f5e6a70ed3c77381d42972c3d467d4346f81a06966c7d4d40f55d60231a6ca76f0630b3d726781201f3f75cc5933456e2c104efdc072b4952535fb0ac49069e73b2f697bd45620e58dfef8ff5781ac7fd0366d39fc71ce5381982b8497b55d7ce6a740be14338bed6bd27129903ab78fc2473d0aa4970c4b2ef5ef31870231788cd4a6bff81bf304a2eb75eb26d558713b81a065fb232f51f6596e02d993d4b8966d96509909c40be9038d2ddbf0242f816cfa8766b2495602297dda86cf9dd7fc404e02692dc624df7fd4e93f4602f9ee32d36e6cd5e51c81d8c7095cbc817cd89f2d69a8f015ce8f1b379ec0851b8851fcb88ff2cbcbad3baa042eda40a83d9d7b93c9dcf90f238cb62370c10672b6942ac58ab993715e0359a3af3cf2fe603ba40662aa9093e2c7a5e1f4e3d2388b3f9608d532f7cb6ca3ef87e17f1c34e8c77d7d547e94c24d7b0664bbb434fad0bda25bc573d8744a9149021766201f74eecf31d4c6ed83b90ce48e25a39725190871e91a73b80cde6166c08d1b30e0620ce4364d21cbb41403b9ff58c253caac1fce1806425d9ac63fcc2e188c3e14f3943e48db4db5ecc7927ff03f73f1055278efbbe8e3e81a7d89b4798cd13a062ebc4092907aafd473e9cfe5505a8117c71ac05be45f9431761cbe2e90e2651ebd7c9b8fde63b840f8a4391fc6d8ebb176de0279767393568a7e50d9d302392ba76af1e3118f216781eca5b6967dbcdee98358304a3ea30fbbadb4e25a347e4a25c652c3c5c51588d9c1ceb26546e5e075809ac08515c871919febdd57811c36a78fc366de2efb5081289faa2e5df6c1c6b7b89802e12c353545656fdc581c64c485142a0a0505ba61012e9e708ab659f2825c3881e452e1b3aaaa7bda390c30bcb8716387175c3481d0b97cd34dfe51d89ce5820944dbd861cd7fcbf48fe2620984b2fc0ef759a3df3e9112c83772313a2b8737b84802d17269a9e61f54ade939945ae0397870810472aab0dfd1634afde1c55c1c81f4f341b28f529c02f7308c74706104b24edbee7c6b27adb70b0e3ce0b46bc08d175f6ce0c68dd3cee3c68d1c5c14816411e2763a9ebfb17341045284fc518ceeab7e647d70318424fc6062feb0d3d4e5442dfc0f3a2ae7ad2a7fb91002d9ae93a7f56349b9080229f5acd4ba5c14d10808e4e3c3bbc92077f68739ad63871812702e7e40acfcbfbacfad3132455cf880dcbaed91f1369826050dc60771d103c48f3afa38a3a2cd453456ed6f73a74ae517e68207640bffb378fb95cb9344c0c50e08bf9b8f432cdc6e5de44207e4f11a39d7351960022e72409a8d2e6b5969f2392faab8c001e1e0e206d45cd8806e84808b1a68800b1a908f3dfef828a6e46329331bb320446d8a2b9597bdfb209505b93aedf981e8675dcc16c5821c2d1f0e0be2541f465d3eb644c0c62bc8c7f12ff9139be376c515e4518beffdddea1e632dd86805f18fa376a37238ef706db082ec072b97ddf67201d771010ed85805c17a3b6f862d55354d4f15c4bf146306bf541f19b2910a725a5c392b51414a7fd47153ff85c70e07a460c70e0af4d0e1c50e0c3420470f2f3e40740af26fc8ede318379b8f5b1ba6207bfac3201eafd1b183157081529033b38f7acd2d98a60a0e17f0e8b1450aa2dd858ba74641d63ff2c3ce0e6ec9f3911c860d51904279c59fc97c9831fd110a625f572a8f7d68e98f72a020dd64fea3781d6d6d939f2067edb4794965ba14d8f00479bc0f5a22b60f23cf3214d8e804f950fac75394f0d02d7f61831375aee6cf9761f96076818d4d1036e6b7bf0fb5bc7df07b035660431384f8b08b1a6f367c77c6f8c27984a16b2313e4ebeff0a63139949c070e16b88e244c90e2a77fd9ff7328b1c075588e1e8d81cbd1a3c33817f4a8b3710946aa2ac6f4b8548fc6c08d1b397a741885962079cc7f592ac50e366d25ccae0a33cf323f1495eeb1ca2976f4b8c5200e1b9420b81fcee6f28ac9a164b43b0942e8ce263f3e8ca15d735ebc173dc41883070e31bc589204c14e357bcca7995fd94810be35ecaaef1f362041ee83f1488d8bf1976cac301a0cf3808d4720a195d5f272dd0715f434a95c923ffae3e3030c1e3b1c00068f1daf8e20a68aeb23bfb831ba5313b0d108f281e689acd18ec70e1c3a7878049478f85ac0062388d34777f182e5b0e9831741cef152663bddee6945d8a167361f7cab8920e95baacaa11dcd224384b1d9ee71a115326762761f42ada3fd7d08f249c585a5cc86201f1f7def05e93fd08f6721484953a50841fa8ce9ac252e3d621f04294a8f6bbc68360441cc3f3eae547a078250e7e10fc7530e08c2975e8ce9531fe7a8191b7f20c5d8d3eda8d9ba2cdaf003413fa7dc173c85fee323fb40b872714d9770015d30a00b0578d139187023f940d43feeb8bdb44f2173f640c858f2c7c1e31ffba1dd7a201f6baf67bafd6ce48194e7d2c77e730ea5318cf3c0718243be9d021f50b08107c2586eb748eb1c3d0d016cdc8154b962bb796a31c4f0a2878e06635dd0a387f30823c770010fb303399a4c1f6b0ea5faefd7815021cbf34e6feed8c7d381509ff7ad99f2617299e660a8e73eaed837c98158196b3167e5dee8ae8d3854a13b6a7657f8710f1c1e3f0e3f8a7d91efbc8198f3bdd2471b738362d691251b2e9aed157b31c7fc03cb8b291fe936da404a1fbfe3c61f860e9b521c24031b6c20dda6d4793f0e9ff3aa105803d2b57eda877ea9819463d33de2fa48ac0edcb861230d644d95fcf8c2ff5188cb0e3070acc08ba389d58113230c2434a8ea2dee871ad987e2f2f12985ff660c011ece821d14b07106e286e5b77c9bed58d163c30c84d91c32f6e15cb40c0437ab28f2c7f93897decee3c840b49ecfdef0181683464b4704c4e0310603c4f01e2e5840116c8c81d807b39becb2ae4f683994d886183efe3f9d8ecf47daf3b0118625d5b5b3331fb4fa1f59c05036be40564b973b1f668c7c36bc40f2b2fedc9a45638a7e1788f930f58f3358d6d9b6b9a0764bc6575f5cf55d9e34011b5b20c9563e98d198d634a407041b5a20f71f7f10cb9f835fc7d8c802a92ffa602eed68d8e85820d985d158af671dfcb27105525bac743f1a1997d735c0041b5620ee75f4df0ff331486ba30ae4a3a4e556e9a5efecb44105b2870b1f3b054f7d14341b5320f5aea4cf778986b79a60430a04e9dab839f9f1868d7e40200d36a2404cd9a1353bb3010552759ecb707ebda92d0ed040171f96001b4f20670d7a7136b4e626b3e10442c5ecb3e89eb10f636ba309e48d0df17ef4a95e7ea8d9600251e552e475fef0e2c62c817c743d23a96526162425902f7766e3053f42602309c49c44352745af8fd28304a27a4ecb8fd78731ae62e308a40c5db5ef9d5aaa4246d80f5b5554fa30446622adfa28cc33fc3f76ea6c1481f417d256664afb605d221056331f4c1f793a0462a899f8742d97fc7321907b2aad56d3e24d7d8240ae199b9ef000811832e7aef1e887715efe01f9d8e259fea30de307b6f980a0d36933b7a4cf5bce460f48b12f84b7c7906afae101f18f34fe8f773a1f851f6b630744cf351d46af0fdbc26a4307649bada89aa894328f0d86e3e8d160dc016ce48018f3a79493fb51ac76cc060ec89a0f2a6fd686f7908ef6e23818388edf80f8d7496cf3a555c69430bcf8c2e0f862874104d8b001b163b07c60e125317684510fb05103a29cfcf14ad8ac4bdf4606b04103d2ff66b1718b132f971c4a9738be18038c1e0d06ef16b320b8653fd8beeff4118e1488210b72fe8a7ed4e1593158b083078e1488d1602425608b58900fef2a1f05f3f5c3b68005e976fb30c386b6b08ee550120387bfc00b1c5fec303abcd82d5e4108b5a8d3f928fc85287328a5208c1d3d7088e185e616ae209b1f1f1f1fe68b77a913d9a215a4f58cabf3dea504b6600539c65419e6f28f3ee7640ea5a43170ece811c618c6575095a34783710ed86215c6cc1f944c49bbfc518a39832d54419efb033fbc59df4e9fb6480529a5098bd9bb2aca7f5490c25f9d1f9fdea49b7f0a92c58c3bfe9dfc68bc3705e1336bbe6f5d0ab299e79883aaa77c6839294829f458fbe696917b1424fdb2cba9ddabca1305313c8b9aaba75cf9a8130a92cffc51f7416f4041da69ddd07ce82748e1c7a9ed4a4eb22ee3099279faab4e161aff0fd30982598c79bfcae931ce09b21f870ebdb3956eed4d103ef6f1911f881ff5775a344190bef0923eea3e8aa864822019cdcc3e774ae98309d2e6fd6aa9d47f183b9720c9e6105fb1e2a6642c4138bf4bf17d34c69e2b413c3f8b39053fd60c325382e80775ea59d755d2e24990f463d6a81ddf32c7254138b17c162d58cd7422418a2556ddf9aeb616481072435ddbaffa3efc08b2fa7af6fab1eb08b2b97cdad5b47c1adb465cc9c22463ce514610cc8fd5cd0f233e5f8e2e829c51ac43375345103e3dfc42f7bcacd64490f72f1f7b0829b7fc2722c8a77eb8169fb5aee63c04a1af3bb84f464a996908e27adc594ef1c3eb5c1682aca331fcbe8976754908d2cb4585ca6a675f711004f95c5f89fbaa1c4341903389f5d6da1fc5b46120887e143572df87f930f20304f18f43c53f0a0b1e33e7fe40f268bb31fed16a3e3ef6fc40f46bcd1b52f557d5f5813c95827d58d0ee2c1d1fc813b7fb9eebcafdb73d900ff447daf269be1f6e7a20a6bba02beea5b1399607f2575b66856a7820feb146590e7ab291da1dc839e6434b1773e733d3ec4054fbc3db3faa607de887d5815029b5b337fbd0525f7420faf5e155ea3ecac791a93990ffe8c4ae2dade65d4b0e018c92a8b42e8522c250201407842171180c062171260013140000000c1c918622d16830ceb3551e148003522a2a44282e1c2a18161a1c148745a260280c06850361401808060402815028245c8863cc3db11ee020ffa08e198ff44caea15a2bbc00bf43718cc9a4d74b0356d840e8b20910a251e1ed7be3824f7afde4d06bcd9d160474282fd016d780484dd2b29fd6d2d12400099903a79a2f96bf24501cbfd048ef844a5d9d9bdcf775253300701c5a9757ae553570b075ff6a98935f74a9f0ebe62c06cdac2250a8996060df2f099315913c8b4d0df1593afed46d1938dd9dd216e816b4961ba0c1eb44a7d02b1dbc8e8f28abe439fb2a836a3bc2e817735e4f9f022dfff5e73b6c5cff4edf34256d0e2cdca7685d3b463c490461f204792bed0889fd1db5a466c99fcca727df40ef64d50e5e1389ce48636e253f9d2e22d87f388c8a437bdadb1802f55a85967de12041bf7a134622e40e57667b61810a4a46ebf3e49cb07190bf1b75c8648c20394fbf4caa9299c67d77984a2203cb714b248e2675a96683d0d1dc87a1969e01c49e40460702a3043bf77ebec9c5979ae5d7283d3257944948f41e7ca404fbda7ee508b1f9cefbad32334d4a87b88b767f5885428d2ea3014f8102ca78aa71bbc8207e57dad929a5b7e48c96e8477034cb5aaef498351cb44072975dd09fb5a644dab186e4a612ac571943e4a34fb993fcd611591928d260bcea91abe8bd1e1041a220f88665c6cfc400d09e67e75a7856fbc20f891ed568e698e0754a6276eb5e7594a78948d4803d210628e66ba55f5eb4569da28214e0ee43ba66abd0490ed76a808bfacbe1caa550f452524178e961b11712c002ba232256fcf43b4ffb3d5267d65a78fad5ea7dd5633f2e5ec09a9ccf50bf8868b6dd8c063af6bfd4f61dc13fa2aefe9d9d65033b66c0c2dec3be0a15a45e0e8cb94bb43a3809effe378855872a39503dbcf780d18e3f8c25f1023775697886027a290378a3a75a08abd330a13328a249e12474331d3a1423571a57354cf8a8cf4d1921b0de7add7fe80f1fe73576e590163eaa427ddca357146a7b0c9659e6d1db5d1069e264d21852e9d6dadf8ade25c9d97ed4d55930110484a66fdb4d749c47aaebf403b1c1df84be6c21464f46de174f1a64d0f1a481bd148cbabb022f26e085c22ff256322d8f34be3627f425f7912495930a5951de19ec9798d82927ad10d940a10cb515a436a72ec6b2e481cf8175fa9ea3034baab13e9763588a25ed1a8aa1b47910ed4842abd6d6c645ac4be06eead10ddd0be1558ab99f8351ecb163334aa3e2552d66e8a9106ccbd0171b21cae070eb2103fcb92d0c6239589028caf596c1df4060089c4982ba399b2903f00a66f36776b7f43cc6272ec3634dd94ba80b168d2bc5c57fc4e21bc028c9ed47074108f30fc1504c862d501241f08911b874c16af1695a4012e8d9b64d15d58020aa151615f4feb31ff6353702bb7b9fbfa89a90a90fd6dc9d4c4f1d337385a3612a14b5c0c0c2f92f9a11e42acb0d8765db75a3233fcef6cd28f0ce942f596d840be035532127f670ae2ca81a86082b186fb28e0f23569a758bd79e020533ed55bca055a49e05c451778e692556a69ba3375d73bb4b8b00cc0f677444c5134f8c560f93a4661fbab4d48c6e7cdd4ae23a6398347083a362c40e577ce02ec42dc6b85e38d1fa370aaf7a333e25144f74bf23a6102d88d75dc6cf00709df670302d57943ad5959ff3a7e298096baafd078b5d143d2883075044f3497c593b3aeba790007dc785a338fdc52fb02856fc673dce4c48d8d38196cd6cda35b9a841088145cca1c31afc0a0010b26c049ddb9b7328b6111479748b4ebd7a6377f0206bb1dadea24d7cdc8419f4550a70bdde420ee86299b82930807002f695f79c23925398136e8294201cc0129e08349220533b5169154548c1d56be550c4781af137f099c71096699797b0d3caead8c2c057615b7d6a9db99dcd4cee1d821a870cfdbae758da70d559cc9cf268a254a354cb592920ecd6e6e6dd27e14b7399c278e39016de8fca73c1773bb166f3713bde98aab17863e94d9ce142c912e51850903b146d6bf0af52dbae3650e10ecdf73b4463e7aa9825d9f7c593e1580c3c2ec1b668bae7d64667bf982f021fd11257c0417fa457aab90380bcf5ffda18889e739a6a0780d440ea23e088c593b8c95a00c21c8df121a464118b06fb8a72b619d2395d315e2b02be1b3c24ed327b5002046e8ff4f73e382a2c3d86c4374f054422f7b01af9ec50150a0c80edc8b58c02d98315da5014a75941619a070e41e2d85f38ff8d7a32acfab1d58a25e0effd89a5544c8ac61457cc4c10699592226f1bf5d28f2e0e94521718ce3e40f1195e063b047c47a6bb0c0700e6312ed81c494951ba77e00420ac34b8b7e041845d891604cf00789d43b7bc1a4d1440908e91ec40e369f3e39ac0ff30d749798641bb2b9bd2711a03f36a2a318dfe6cf0b67d31f90f189d80e01fc54dcdb5233a3ee27c4e17a480588e8992388b9ab0ac6247438908e75185bc67604de1a0af4eb22041b426342bc2844fff8c974447c2845f9ac29379119964c35bb838e28263932408ac29c804827fd29745b658bc1662e4cf2a4d07f30c2639a85fcb1d7d39addf7f17f59b2e6fbc4e26b96f21d0ccb4e077c63170f19184b23948b9ed3793747110651a9530b11e4731288a32c24681094ba2dfa491de5ba6b2b8b1c9333a8b504f88e75803594d44134219903cb5d566fb534b74bbfd98b819561a21535ce039750e20f88bf4448dbedb84bd82f157d6148ae4c314e8d886ebc8d6c1d654f28ab0873b6038fe2a0c537cd09bcf216fd1ef4791f9ea28bec289c2fea6d006e4f25d4cd7307ae87e97f2516ee21aeb4c8a1f04c399fc2a6133c1a0fe1dbdd6cd31d1f0f74eb9cef0c016e2faf2865e478fefa389c73a686887025ea080046246517aadaee76c4c290fb731c82fe5a6c3a95b62b704952e2964336a4d3acb60ec953220260cdb6c0cd105ab7d33a6819ad5dbd035bc9111bea848fc2b314a3c8c083cb0b54b4cb18ceac6a48d62d1b3441b4f1ba58da0468da1cfb8da686d4c6df8aa15f48a215844582f5941437a20470f6b80b38a40e877b9c424b035180fc6829dc132b1340c2806899161941833060283c010307e18ebcc1443cef253272c30568bb1633830080c72648a291dd69e9e976f686664fb5a122c000e6287f934c2e78097eadc11dc8d7a179535c29bd75a50b5ef7be74c22623defc313f7cf2807a6eef67e4a62e84621a9bb6d28c9a24e2734d847a3942cdb8e143d30c126540aca127d54a712cea1b833bde6ca5ae8d802c5274658251cdadfdce86942420dc2728f55f6f99cbdf135c5b20c65c211b52362ce49e12a7416ed98a42f5ec7d6fdab453c7edee057bb82a9dc673353717435d57ad3f9ade7eedfa9373fa5def99b3ae591a7c2fa38a9d8947f05550843d707fc8e9030cda301a1b6f581b8440c016be294d393a64db9959765cea04ec501116bb676405e038a84d5d15208cc6276980d89e02445b0aabf131a2d429459f82514025cdbb213b2e2eb2c325fb7ac69f2f0ca0cfa3e3262393a8e0ed13192a6e05ee2aef8ae087725060a5c63a7f0c04c6e9f33a4a02a699d9d93f3803116cb07a38876a275cedc353e89acf273b0037a4912160320bc0ae86d2d40f282426e446a8bb709ab65ab2c7f7f4394bdb991816b08eec3a697a1094ca7823ed2400878ac5b8e9460443ed1f7e76cd1683ae13e39c03efb37816a4212440625660072d2c59a0a53a9672ce0bddeaab4399e3e3e5401307f37c1efd16549072b2a4346bc5ea8825395415e8a5947cccccfbb0c420af3263019afbf2fc53e81718a2454245cc214bc48d084507f980271e7680f1ed2f97633408dffdeffa5e35d573e3743b6a9868a913f5680868ad490c6fe5296f0506df96736e0b7f81b798d0ac371967082a0308705c62eb006b35e1b5bd51222f035577d1ba85bd40d49e3c5583d2b488ed501c875015a2350145c2b556523540aa0ae77d90cb32adeb9d264b0d7cd0cfd0e0d142bb32a5b4e6a8645ab8d94671bac6d058c20037a464df4466055e4ef38bf15787419ac039b816a840dd311a1b534c391c792700bb3c3fcc80c51fcee082e427e073322b79a1b8a8667d881a9368ad85a93c4d4c9eada0a64e672381136f70c910222316161c065953686363ca91d983d3ca00598032602ab84584328298be3c386f6d67b63bd91ade1ee506f406bb03bccae11de1c88c5ce8577918998e22336a03b6c636fb3b7b4b55e8e65c66937c254e6576fe40888c36ff831da02eadd8a77659bbcadbc71de186f276f99b726a62102b8d53b088c656271180106349690046306da315363f3b1bdd8cc68d43b63448a0cbc60b9a9b879dc1c6e266e1a9a1226302699b80284b2b912b52586ad40022515d3db94d70aea6ae1e0db2ad8085fdd6588ab6086c87c75efed33967b336573cb4670592145c4abe0b27d315d3124ddbd7ac2313fcc0ff9713626e2b5d01ddaf922efe05ad884b2eb0334bfdb77aa0fecc3b5fb68f7be37c7d4f6d626eb2ae5bc589622c922fb8e0ea4a3537766c617ba25406e43d3d62332285acaa63c9f653c8b05ddc84c1377c10d3cfa464417a98ca22eaec7ea71192b71130b624ff59115cd4ef87e49afd363d3469f4784505ecd77f8281a014da0785cad99d70c8127a55137c5f0f83fa6671ff198663bb1082440c44b1f15ca19d903fa359f5e9b3685253906c29ec4a92c00179f1f71135e1a0ac3a00da761247ec4d46a07588350388d62aa8207e1713c16c645b054046fdf0cd11c5b6791f7a428cd7d22b88f2a4fdbe5d33d7edfd6aac9ac1f9af01e491f3d51cfd594af35a692953175515c8883e3481ca37066790604d45fd697fab81a2f2b321fe13c4a57d4d1228fd61ac8a2bb438655dd76e6cc84485993e83ba0228598c2554be32136c443b899cc7a6f7625116d97eb8ebb23bb53eccbdfd2073a1b69a69f2974391caac581d26ab7d741613033e2a4af26081c9de35159eb796ef41311950b167bb05fc8e986464d3d8c77042b2d094ef6efa1c1ecfdc9a57b183200f008dfe8f3d004580b6674f744bc8efaea130143a33d54aa7ebb69a700d812976d190b7026dce0af3383c20442c783de7edf22faa8e23da9519c9a8be3e67dd17980995f2611a6fc0e187587af420df39e5509325432497a7ec1ce02e25ec421a9dd68cada9c2842bdafff62e42ddf561df607cdceb40a3fa339dcd5330f542c27d9bf8b1665dbd20959e34052434dd605a7ae5536446db55e6aba09afcc8457f26dfb883663336226f3a962b3921c61698b824a555ac43a3404852653e26116309bd558dd2e1a2ae382dd0f778d42ea00300d8b85842e41cb254ca41efd8f9b990cd84c0f013f22c294fe6568126960602d0a09fb9ee077e3b1eb5ee16431310c588d4c9ab41b252ff532b304e541a76b5e29c6ef0171f1ba79aa8b12ac1aa89cfdc6480b83f9dc10d0d277b404706efed22b3d66575e6535581b079660663e146b443ea94a45b30600c19d270bc9cba4a0cb38044afc1c210d04dbe30ed1cb8ac9b44debe78251e5d8f06a13b04f838c763cdc1a3d771dd9684c00ec44f063fcfec428847d66564b59fca8a83cb2045fb0336f777c4a90c2992676c9052b257e3924d25cc3ba0155b3afac1e4244e1bdf70aa7ea068f4214bf8adbca628b1da22b22676e1c080851ab853471f6cb0ea2b72a25d5dab091e9c422d6babccee5e58d2881b79c331ecb82805c8083c2e255de6baa86247ba49702f1f180ec59baeb94209c77f4c3c2d19b1045d1eb19eda330aae00e2fc2414c818406e0f3aefb76ca83e21a471e98544aaaddd392d06605e99ae20e2d9bfb329c1551ef19b54ef1581b5efd87775a755b181a59babac3096ff6074d65c38e34fd659e55706965f51dc9707f1b4cc25d8d80205af1eef2c122388dd10ae44f6be54c5153b72cc1cf65eb6353cb856d295a83ea39231b1de9f2e09cab36d0715b6cda7b1e19f8e769d0c4989be97af5e50e2af9225e6e20188b2fe75a3dced1805551b7f7dc5f222eb08e136d231c0451f9fe4a2c2a4abcfc435628dbc76608efbc4e1a53e75d29a50256ce0471f3d48dbaac4f8e4e13cebb5e5bbd2c222cfc67831f60625b89081552ce30eb99654552c794491a8604b889e310e62d8e0ee0442f3f3e7e8c406167333db1b5ecef873d06c584f80a137c6d03e0f1688abb97605ea9888631bb9fd3e406aa241806eea064628154dc278627335275a4ed371335f8c72761d9b66153bb522545684269f57e6cdb95033a7c0e9d412601c66f9e61e27394597283176d8e16acbea51b51600ce2a6697811826b113873da17602685e90c0bf812bef1cf3f9f34a07d048edf41e52aafc1104037c5060ea037161d3efb0b0e14a5cac0102c50a450efe17b6a591999429d9ca0e42b406328570b996579726bd89e5acc914ca0283fda1e0ca200492b76e825a5ae4921310d69ab0907cf3c2c60459092c9dc3925478b4c343334748dd06518ec07089fa7e8c1f6fa63dac97fe28d90f5bfbfb68a424157fcddca1918f91ec90e6d9e8b4e8e2d901618b24ee7cc6c0556221269f1c26db740e8656cc25dd46052b6eeac6c86908b4b229b4e1d852bb33ba0abfed867b79e64795b24bc4e871a934b4086fcf06e627cb46561a53a585b3aaf9677b280e262ae02b980b6f7f66c45f2e6b215c2259bc9054bdd009f597ceffafd2169fa06041b2c58491ea3278a96f75a693d54585ee4d6e51492c9c5ba565a6dc7da8df130ccaf5bb25cc3e0254cb3234a799a84b77d2b29e8d743329f5d34d5acb2c64aebd6e740346a793695c50703e8785588f7604c6c69960e78ac98f0e10e38a57ea35907125b193b0a65721672578eac60618414a7ddc9517c8ea21c0b9f92ce1dea57476cb3cb4b5c452b1262d9f2a71b8b10e3a045ac13c034e0c670cdc2249d0778f211390d9a7703bdd8f369a04e21ff2ad9bf3c5ac27125b71b768f67f6a4881725c73623e48112b7b754633d57b3b9cffbeb71ffa20e80868291526dd4b78ec9efc1f01bab57142cd8cb8cef000682846493e140caa64559949a1cf6cfb4c5ca5ecc4947b9dfb771601613f3f5822a5d967d7a32a3bbe38e31cb1682b21cbbd2e73bf49cdfb1b4e594c7f2fc792c694ea5c9168e40ed7ecdfda12bd02f15347985bd809b95291463316c8738805c120e90d79e66e986d2e7a3e6ff5b75b277f3860612821369807d0a4d3e0bcd0cd5e55b82ffa4c12dcd63e02f68c91f141f4a722d009f3105b34912851e3295dad43075eeb7014982482a7e14176b03b424159501f3320f1ead23c84ef359580c2708e833290b0ed78ea82e71696c115fc4d0c75bc1062de96a5785d160e81d0b5b15ba66f7d2d484491c85609b57c19cb812551652f4ea51a52de2116ca12695d04173c9a077b8841cc6da4e74119f2c479a0ee0c3572374bd32ea396709a6acb21e23ca1d7cbc67a429b72d9755818f59903c4a257609c1e680ceaf32bcf9036bc6c4a8b33efe53cecae282671f27bde8a84118a94b72209bffaf7a96a65265a53998a937b857c1b1be3b2b80c8855d92ff4aa6b39efd464b5240fa46800011c5d2baf3e908323f52fb92efe2ad0899a27347b60375c39b65ca0cfc63fdf900764bb0bd83ec4b962ac2821a37ee5a5171745972a5481f62b4a50526f1fdb74398dffd4b02143012ca48d97dfc3aa992874123417452c65b7c1a09f1c0f5f84aaf638bf119c428d0606b31ac06b06a17035cf977bb49ae1284e6138c026413fc6cc34644e2ca4ea82220f506340173cfa1ab9a437f8ae5969965db71fc47c3d1d91f84f96d1472aa78a62f20937e69fec426f197e573598e5b05093a5315cc9e418cc38ad857689e380745efdda4d43eb1cd519db861634d8f634539a794471a3eff9521ae4851e6f561c7f77b158ed188e7999f159e6e7315a75d97d0a6f9931dc881bf91c1e911e52e1a87f35f2f69c9f18b82c3fa88528fe6548cfd95ee1b1192dd79d8b765abc706159e55674356c2f783abe179f1b050fc33d7119487b8790ae75b14b34b69cfdacdb0d278323b66c492db5ce7d04bf52c5ff89cedacf305a1eb813d0461e9cfd0ab1d390e28cb8d6196a2eb650e30751a13ab5e63019984c252ac210352f2f8f7f76ca65f803c844286bdc68e64f0f3ad3cbe65b72daa931b446cd46b0a639bd5df75835951c6c5e8648c0ba9686f2e931ebd6d4e0b4decf949d2e0ed4829fcfc333364704058848b926b29c4562db775a7b0b02b7394d413d38e0c1169aa27a90d029309b3e78e80ba26336c0d35a9221d3204ee1963870e9f89c2977126be44e334079dd746e85df55277fdba6970ebdf75cb948e154438074eaf712589585399f0faf05de37ad800de8e636c67a161d9845ac441b8874ec00542fd72326d4270ef1454f9afee467302c5f9f6cd3e68ad4d2c4c265a05b7540a155307808d2b445a71dcd1ea57cc5fd54ada67c23faec811863f45ca6c663550a8480689cf6fe8dfde521a60dda42d526b6484541636de1f20c623cb4f24615481a3b69d5698ecb093cb1c154f75e6e1d04c772c8946246879050ebf0c4bc3e4b5f96d937b748c53f698ef33e1d736f8881847f626f903e01757008c7870e53bd3c0f82a22542474cabf0d754e41b1d2870cb29860175dd9b8555dc1e5f7f9300e3aec54e4d28511a33fc8e81727cc2ec153554541da5efebfa554fb821d390591abb0c193049c9f2dd1a7e8872f2554806860e9c24c7e8a86ae468e86671c0054da09a1e5e0cf5be0db22c42f573ba67083d079c45d53e01464bb6dc7aa27e2a163529395fd944f977d62ab890808955299c6e6f8c64cd284e461ae9e0a23c2976a35aa92871b91a88927aa0783b218b6d6f3b8a11c5b32c0e3fe434edc5e9e694e92af9159d836f884d778a0c3373fb19d682ab015b8ffe650811b33192f7254311e32e8510914419d5a1bcdf132dd556d50e23038bd321c62c9e482cd27d5b585ae14c7cd5d278c4d1cd2b9b0869d4162486f7b02d91ba65ce8c638d2d45178eece632a8e2f7fa3c6c3ed58a1c6d8423d9de195a254cfce2907ca2a4599c98695c9eefe40088081af9778b815eeae9671bb8ae882113355de50e43a1201d30bd06233719b34ae1d7af4df2564129918487d39f98083626b84bf0125d8a7cb6c2a08f687c7054882101b720bdbe5105e436e1a92705c783d09460bf5ae413ba843206e60c45b86dcb54a3e93af648d5b9bc91b811d4f405500d3660063b0a910c277ec508ee874ac8784e45f19266519e4c1824b7f8195e30d7b4a09af44ce38f50548cd6a87a0b18d1f4ebfc352d6cea219ed6f82fc082a6b19750ca0b1a3f4126c203edd43df4d1f2c4534293f244ebe6a7ebd0577739563d72641c89ab401373bafa593157884bed469c6a9016d78e019d90ff28d7eac0de99af0cdd1a8518f031644afd8dba6a35675419284b1bd3578f27b41632889e2ccac7cda8fe209c267db1d235dd1d500b5be1a040fd7faea050fad7efed44c8012eeab272820e12c98aa8d82b85d6f964f587598aaff4e7579544f9d3a90a97fa52e4c6aba4ea3ecda3069a91b6c7b58c046327ac0326bcb2544852c4338a04d2821b172d9bdb9862125082d7fae6befa308d776e6574696cbcfe7c1fd78b945e383134d5a12b5685f15cad78c443969aef94bf3691b7a7fd8b6779a95d3d28840236fa388296c7d28095da5b978ec0a1bc0acfa6b7b22daa66c6c35831e612171037656ac1f1ae488618a65381cdf4dfa8753bbbd4a90113ac0cfa419a5224831e3253acb5e6d980b6cb53a7e9b917d93d881e35d3b83cef15fbaf26d47aef939d140442263a065c3faf8d0b0223e0a9d422b500ad1aae4c99bc14c17e24544a83071d256251e721e93a50ca8c89c88daf2e8b0fd1e8dd5ba46ae77266237afbf617c6a5f5acdb424b67beee25fba887e082cbf1adb6a5a6e0c12730b2aeee7357c4b698ed83f01487660d473dc0dc60d6537c7423c74ef48127b8ae82312e3efe0605da67f1a0bac532c4e02701b5993c10aeeb01cab024d1094cc8d10de423441c9cf576a27c253af9fd1334831b1220b6e216c57cd1f521fba5720d006af991d704edc7d6f88022eb9fb19855dfc1c812ad6afed6b75d6ad43b8194f88aba64186fb32678bda2296901d51e4415f6f75982aa5452ee6e452d1f68bcc092103d2867a0ad122ff5e465bdd9a82e1bf46dccb38601cb004b923ee4b2bd8670d877d296476ec405fda3846481fae4434e2fd1f644a1e1774f9c8d755f25eb861ef06ffd8fc854eaf11eb066a254df22e38d8b3172e67f2bc5794836117d7979a56790609bd7a40d907045ace8753504577fd64c31afe01b3ae39a3a93cd27ce4b48255baf246237e8ace1ef251f047c044781ffb2879f0b6d7e4ef9751c5a01deacd4025cd0ad50ed33a8666612382ac28ee8d7e26084a3f2752d42133e4f3c5c48b51c8a454c8688380a1b204bb004cce3dcd45d1c6cb8e9b510101d3a8c9f364d15850c2f85409433c21ca10792f9131eedc843687ba9bc2bcff5c911d64070adc48b571c1bcf829e614ec625c3f6f6088887d27884605a793883048ecbe9e8db1b0eb0d1e4dc8217a236c91d88a4a9c38524e3cd8ac30824f1aaca50a26ed7348523968d04a8a43138dcf9275d991bbd52320664dadbaf3ef935987a2ac05401298fcc0e1e5b2625661710794191107e7f207a3f0d457da0ec7e33635adbd6c5bbfc60d60cd0198ba5293d436359f0fba81a2ffa70020e428d0d217c0d3136c132dbd0699822a8e9bd76af2f13f7321b10817622ea759c1311222f80a07f8c9383578a57248ca72546097db6c0858dce2cd048b3e384d67686216de05ad5c45afd7377bd317b75a3da5a6fef4935887ad8a95fb3e72b82e0362199433965b79686b27eedc7a04bc03f5e263bdf8907af1b146bdf8707af14112b4f80ca7c98a10ff9729d22f68c802892c31f2c4d043701e041738e81e307dcce8bc2a143973b9d76c25701fd45b1f37d6ebc763e975741c00ead7192ee963e3c3e5c54278b42efb2ed79d8bf24eef788506c0b885aa3c387f833e4e770fed267b98df28ff6a631c9eae32ee77bf598353c2d09cd7860a515f65f148db9011107718abfb91d8ffda0a742c2b16b8a103302a8a14ece2879c9ecf1bb7716d1f2b57750bb59873a3e32390e12a385fe43e15eb12dd3c13db5edaefd0c9214f3a7a9024615eee816551477d75d80e7d4a629f1795ec2227fbe0ffd367c2a685cd59cd01d6a802055e31eb6f1e5a119c92f7bd3000a242b0c479eb7d7fa4b078c166107304f664ae3fa97ac15039018141254d4816f3cb6abbe1b0fd2e1e6daf31f734af749fe8edbf6ccc7b10858825a2a2ee08d31b3f74bd224de3d1ee7fae628c5282ad05360e126761cc8403d686401b38fadb045014d6cf65239ad0d42ee6ec49930c49e4749f16a4e96e30c4084bb0dfa8015230ae61d12a2c0edc087e36e49856cf1900da374512cf1026e7459b0d7132f1633153f1ab4405bf5300ed847552c7197893da95617486c135680ab624d19f17616db2a204ac289844dcecf5bbeef6fa52c27e411a25862b2405d3a3c015a48703626b7a71c9008245832ce9b48a4257dcd8d9d04746a7fad05caf39bd010f158fcd6c77fd32fd8dd8e8b4278932a32228b1492b4a5c7a7f2fe82450cf2a13f3f18e76affeae9461c47654f7ca002806e700253d913bf0a9d66a3fd4826e3494d12d663e788e83fc0fb16cf3d08045820eb0128325c18188b21325b5ce5c60af5b6e0ae43743ae56e12beeba7f045af1907b82bf1c56768ef963c8ec24e2f2666d8ebc683a7e566d2615eca75967979a6dc0f26c595dceb08ee96c03811a1a4821d3c8df10e0c05ab41605c28b2ce9dedf1019f981b290d2ced36e728fb2c2bf0fb1453752132e0555c5a7f2b5d233e44eaa06193a816844b3f1138d5e6bbcce3c74f69c1c9cd3c791b65a619fb7b25ac875e127f207f461be3893d646665495e980686e86c6351006702f79f01f2bc03bc64a2ffb9c80def4249b90a869ec028d0cf5c144341ede9220b869470c6b4fe98089f4d73e336a7aa78963b7bd9f2cc071a664116799f61f415467eaf860d327e6a594c717699f150b83c8bf031605ce0494419fc1e1f01e15cb3edd5892d893cc5fae394087844e0a14eeb8b7f35e5dc68d491455b366e0fcd29e7204e1d62691d57d434cf57d5f1fa80e2c80de16395888fd0cdfff59282b2823820175a02bd888c295aa055f2372fb7eb8ea611cd4e4d4c21007a69a01d86284b993b3bbc856609dc5d03ca9b83cc38e4c50f0951e630b0155050a2ca2bae770be5a9d7286adc4d43cc262195a001c0a2e1eefee7b12af30d47ef7969e4dd1d9bb780eb307053be2a014a2c68d7588ed81663b258048d4f65314660640eb7b00910058788960d46dadcbbfb2b4a631b8b9d509432623044ed2696b5fb3a7fde4ffe8c49c2395b51d166f1614b0ebb67e3f8d30df6c0488d5a8cb4f2320463f2efadde9274abf92fae95b7f09be2bb45398fd7ff959ad8feec5af6820f4b64b1f577d85be7ec5891fb03667821375e36fa202694dd463f65a2511f592d0f5b4b85a51851fba6a6d1f9c5ecc3c7299a92c0eb87881ecbd8a500b214593e1fd8097bd205b6363880130bd0b012a218e563150b46439fcffffffffffff7ffa561012407e6921542629955cbac562bdc02b534a32a59454eddd0202d874fb1beda0328a750526051405e47cd2e49945dd030d8dc6c7e7e78c7a9ca113d2847b99555ff0d7c30c6d8a24475bacb80a0bf12843739e420effd025bb821e6468f49f4e901f49a804798ca14b22e88a9ebe1e6268b464f3d21b93e74ad60dc1230c6d6f36ef9892ce659f3dc0d0cc0719e932c2194bbed076951c1db92a78ab5fe6853e269155f368cad7295de82aa79c9b8953cbaeadc0830b8d50ebcd213dc96049c5b185aed3e2b35f2edd1e492d74527447125f95196b7559e84dd683ee99907773e6010f2c74a2629aba8faa70f22b346ba259bb5258b43659a1dfcc1ce654cc5476ac022636caf978c8ce5863167850a149dd92e287c9102cb826f098425741e774939b64294d99b176c31f8c433ee021854e9664ed98a134230acd5b78a8784ffa440c29010c64dcc0030aed68dc99aba41b4a784fe3468e7481c7131a912f68097fb9be41ea84366c9f12f9839efd313d9ad09b85551393440f267429dc7b338699f86f96d09696f6e83eefb0ce2ba1cd1e1992649f1852c467ac25228f24b4a3c3e51849554f63833c810712fa144e08f5dc7954a896028f233442375b673644f8cb083c8cd0a669d4702995f80add8c3519d80003b73d8ad06b6a79063f9dbaa9a14468cbf465c971f205116dc6daff8d9405041e43e882ae189a75e753de6c43e02184ded74fef778510e34866ac05a18d94933b689838327266acb1083c80d0a725cf0ddaafb4fa41a3533e991584b895eccc5853721e3ee862bc9454c382e8c678c65a990c193970d8988183d75b1274f442060d68c8e00d033a78d1b18b0e5dece5ddba2512e73d3376031db928a4946c31932b725750cbfcf84b19afd4810bbb45f39b5bb297acf9ba050c30688ce08b2f6cd09011060e628b2ec794a23b49a8ec13921681157c20071bc081c34fc0858e5a7422aaf9ac4fa691920c1b39684023ad0660780b42e0010fd4c0e137f8e28b937155e8a0459f43d490202e97e5592374cca2eb8ded977232ef1c3e8e93456fb9c5c3bf8f8e18532c9aa4736a12b23958c40e169d790ccf21e5e777b9af5082b665586b25cb1ac935af755b3f5eccdf15ed59e8fcac5fff87bc157cb8954a2a4b252f29bcc9de507a2e8598ddebd0c18af6435ed6dc245f456b21c91052ec974fda54451fd552bce42b5515f254b4124789ce99544b8b5a830e54f4396450222815bdbf298e8e53b49bbfbde14d844a49ee30459b74523e2969e61e594bd1e5868896d990a2cdab8c982495523a9a46d187ac2a4bda6351f439ef46d0ab64e6a785a2513245d1b173357770f020f8e20b84839f713a40d1064dea13244a2ed0f109ecf0447fa5944861c2075132a5138de637dd229f37458b72a20d9aca4fa9bc322b5dc7263a34c157eea0b35ad6bf8c6626eaf876919c595a1d57ad5f9e8449491e2cd28109940a59e6f227edb844df79fc25043d32b2263b2cd1e8f6e04929a5a7c52f9568c333e9e88c98217e28d1f678e54c9ee4269bec984413840c3e9b625c4775100d1b2dd01a1d92685210a6f9c486d822d1956191688486edbcbe14489047f46d21e9105a3bf4524c87239a8f1a51644adec881c303298840472398750b13b3bb2d8bb9ca580eb93da798a7542c607430a23f939b61627ea7f8df093a16d1ef574893302af2a6d2a1883e6e6cfbff873fe848441f84054995247e89d0d9207420a28da53f5ac256ac9cf387e8a3474cb24af85f88abcf6082030757a0c3107d12619ea4c6a49b37a5105d9864d9cdf39f4b5342b4495f2bec88866953221363c8d2eff204d1e7c89ca94c74ce433e10c858980929c4d32dab4afb54528f7ffa490888beb25c9b5b79cb6aaee30f6d75d229df1a933825e9f0436322cb07b14e13b32cb2123afad0c57f10d9123e6ebb96c306182af8e20b198623870d3070b0103af8d08bc9e8c78fa1c394f6d059fca4d7b13299f41c3d34fe22f28e92e961b3a311a8586d62a171f53c38344aa594b5b8797cb437f411652f54de0d5d5226f47a38912f5bdb863e89f777102236343967ff18ccb3865633e8d4f1115743937a65adf78376ad681aea2dd9cc574143675ae73728dd3f2a7c86ce43fca493d2dc94ac19da3fb1c8f9dd4b8ecad0c4aa4c59e4e77e56c9d05feecf5f95a66243c6d06895f293e661bd11435725d4d367e4853086a1fd9e4d0b3a676e070ced5a9edd3ce1acd32ff43def6ac1f36fa4ce0b5d503a7468c489d1ef42273c7e107fd14b698c0b5d58d52f113d6773a55b68849e121f84bc14455a68de435eadf495cc4d161ad74af5d5ce1216da8d9daf4cc99d7db942a374354cd26ab1648566c564d0d26a393f57a10fa9543f930815faeb8e1cb7cb29f42659363735e9ca4129b41d2f7f258498b9228942bf95412fc6ed985447a0d0f5e75441a518794297b3e6b0de57cf7b112734315cd0129fbd4c4a4de84b9abca4d14592d033a133b50e7d5ab484ce7479a86ab21c4f47094d8ac194a78e49c2394533eb383148e8cf34e94e9a2e6e9a23f49ab4c78c65ecfb6e84f6c4a9f5886c25522e429b8292112d9e2445970897c7e8103a55213773b29461a342e8f537e83cad06a14b32637f63a6be100284ae5d2d5426a13a8e7ed0c8b4ec314643c4e5013e60c3d98bc6238809274c5e24f1a28dbd78bae2f387c57217bdc5bcde2985d07f5aeaa2d94c15532969ece673e1588861cdc4c745a7794314af144b2ce8168caa36e939638b4672498f542ddaa4b5e8fa534c91b777c6a4451b82d857edcc2cfa52299bb81097459ff3e9eccf792c7a53d58fcf9e9fc9028b4e4fe8043d231ac4fb8a2efbf37a75d415cde7383ae58c395b9cd88a5ee3e282d21e25871059d186eaa44b3dc455f42931bb9ea89b3215aaa29764ba7bc35e8898948a3e56e9bb48eb714b42852922ba4698a4537459219b9e544c29c8149dd0efa9fc3c26eaa5e833663fcd965974f290a209ea11729a89857546d19f5039490aadb8611145fb1d266e08fdcdb084a24f2dd9a3c498fd3c281ac9417a5229b95b873fd15cd64f35e51274664f341eb45f0e3acce9ec76a29314c2e9382e271a99648c2b266a2ad24df42733a66691f3d0441f2e266759af66089e4726fa9271f2ebc9ea38f13c30d16e9c109ade6f3185e771894676aac8579954553b0f4b742dc983c71829e2b3f3a844bb21879e1833ade2751e9468c207f98a381f3159e731897e56ad43b84ec863320f499054ec8c60f948f4715939a80c41651741a2bddc2165a497cbe811ed064bdd396eae70398e68fce46ecfa634a21fd34ead2366449f9594e558991ca65d44733aaa755663734715d1868aa4b36a229a5c563a4c9209320711ad46d2a6c287e8b407f90cf39752140dd1a508f9cae26785e8628730a7afa76e9e11a24d1d777450e141f45a9a7d833e11d108a2494ae6fc212706110d449365467578d63fd300d1a59c21b7577eca92b23fb41f938ae1dd7da2e3873e430ab7ae704a58521f1a217298d1f4f9f088b21c26ff670f7d7fa4f0eb6b32a6470f4d166b31ef73cdbdc9437f493d975261f29f060ffd259967920aab9ae277687264bdeaa8e356623b7491648a16f3d6a14dc233bcf9c8984d4a872e5e0ee2d363704f9a43172dc7d4a9277268945cc9109ae211873ea745f3ee6b6bad3ae00187467789a4b429d9e30d9de81de591523e74aa66acdd9041031b345e06376ca443bb1b9f83467143a7cf3f8b7a5ccd3d8dcba30dbd67cd243e96cc994bc6c1b0f460431f4f5406572b5da2e733e2b1862e9e4e31c605b971544e881afaa0a18476f88fbb710a419a0df0060d1961e0488f3474999548130b1e1aa5b77204531acee73b743286b82b4958d010b643df5bfa2d47de324fead048cd5fb1081dfa1c33c6d09922c339872678cc4b2a8b5c16e5d09910ba4267337dc4a1f1cfaf49c8aeee3ca60f3878d9474b6f68934e31fb6d72155a72431fe43d920a728278511b3ab9e47bb2539c9c870d5dc84105a92574be75d6d07748c9c7cbfcaf3a6ae8744ccd1d29a9cc6bd2d06f92cd312e54bc67d0d05f9297ff3a3f432b3a554a10ca837ad80cbd57e6cbb7e71c2e2e439f548946497235474b86ce42afb7a7ac12441c43f332b9b5b455ceaa18fa2473feb95be495178656348e464af2638207867ef264bfae9093bafb2ff47b9a6243ddf7429fa32d284d417252a9ef429f25f424d520e2921217baf45466fe27b485c6c7f73f3f89e8318fb4d05faac50b09ca429769e2f35b21fe3c58e86362e5d1d89784d6afd0e7afb8da6542a8ceadd0e614497d3628a11fae4217ddb3c598227575a542ef1673e95b0e6234640a4d1093b3c5d0ba321e92426329fbef767ec8d150149aa0f73769f66096495068bbe464879427c9f5099d68057da132f5d377422be2928f3c0922b36f429f2fe68ca8ec29a61013faaed2a41e2e6a3c9925b41ada6e41e91f1f192574b9bdf212b2060d79127a491e496ff89c6773243466422def856a69ca11faee9261c3c94668dee449ae32a153bc45e83ac40c95357688d07e901f494ed210babc3962b1db22742134b9a327d3d8fb9512842e589748906d41e607429b59c47886d0c70f1a131276fbb3668ae6c3076d7f8b9c9fa03a737ad107b11ff1a285178dfb27ad49bca98a6517ed958e1b5ad1459342e9c66a7ac879ce453f22be8ce5e839678b8b4e88df581d7c5c77bd451ba39ad24d5f91576dd1ec6f9796d0e9d9d35a743116f329a564ec10a145ef39a394c81fa25c328bc6f547ce07dd5c7acaa28f6eea331b5673d2c5a24b593577305f73cfc1a2dd88ed2eeebda22f6516a21eca343357f41652546fe96c5a54a6159d9c14c48649195670e6f99318effd01ab68732c1d2cdc224790fb0354d1990c539931a74a62f707a4a28f92424cb1fdac53f707a0e2dfedff3ce59ea2b37c216a9bd09d3c314513f63dafeffc259d2b45132c83282573e9133252b4f3713d629697d0b15134275a6d99156370cb45d155d0ed196e796d9687a24932987ffba924c17250b471ad4f7370d3f7ca3fd19a30ef129adf23f99e6844b0e0bdd5d954893ad1fbe53cd22d7b773f9ce863e8a6e8e8d944f31a553b538ac5b0d1449723f39ccb06953f134dea28a737cec9741d13fdb5090fea29e512cdc6a0925cb158a28f3163d01b633413da4af41934537f92b58e5b28d1a89ee89f629cd221299368b6f2abeb762c9191228936a7902de73a34246589441b416cca5787f03f0b24ba903fb6860a4a744e7a447feef9e3458a1031724497637979885123ba1262763ce5f0233f8c685b74be389fd593ec8b683d72e9b831a9cca828a2df8f3163e8c8c8cc26a29113b6f24714114d6ad1abf029e1427288ae63705352548e7cd0105d070d9a563af5ba85e8c45f5299fe9c8284107d901ed364549fb8dd207aaf6caa1564f3bf268836e91043e64b20da089a8297ae18106cac0abbd9f47f68e3f829615a547eebfdd07f4815fe53b0f826ea4317aaa19255c7e4dcf0a14bb1171355376aef7be8a34c3ccd12d743573969d333394af03c0fbdc8ab247d49c76d0b1e7acb792e6386efd07ac849e9fb7afe8aedd0e7f2f6ea54ebd05eaad826644e8a13a143bbd1d523f6c94b139943ef671d3f46d6711fe5d08c5ccfdc79c9217471e8132847812c0ae4d54180bc9252658a0420505ff07e611f27078e1adc284000726028a08000e0b8f1a706c8c68d301280011c34c2700504e0120008002f400300c081c37c410001780e475f1420009ec3918d1b5f1c0000037040026c84216306c773e090216301000840030e10468d34208d1c60a0e10c9d4a99e9a99497b67ccd58631bd04038ccd0c64bcc29e24b92bea3196b0c460d6a600e1ad0b0408c32a00c04c420430ec2386348400c311c20461890468e1b0988010619321010e30b7ebeea93bb09a3c6f1429b3d65080d41458839ea426b9aa49b182dbdc1f335bec66a0c2e74e65741247929861885716a7c0dadf135488c2d74a9cc73bf5b3a63f9ac066732503114c4d0428ec751e36b182046167ab14c918d1a58e84fe8bb24eb8939fc6b031aa8063264141bd04035c82b242086155af91475e45f4355536054a19f1c2905f9f65c8fe20d2af4e71a835e48625584347280310387cb905172241a3370901c34a081386840230718346a6064dc7830688451c3c6d929f4a161b299e847f5102d066248a111dd419a2915c78842938466b950fae667b9185068dfd53cc709f9df70c98ff184f692251996b224f90f9dd087c52ad3b1c3735597da843e65e2e49c27c284fe35640ae51673b87e89622ca1f32c8bd865324689320d68a0152534daa25573a99fec0e1fbf911204396a78a0062746123ad3aa9cb34a67aca966200612ba546ee131f7955a0218c888418c2334218e98284ab354d713c30819a308bd7ccb6eff5a90a54c19d6831844e8a2c6a46a32fb184368a3ce9630dfed90776308a15895afc2c28f118446069d4c08112744ca9a92841840e8b2ee8a88233cbfbc951186c7f8411783c68cf11442595463f8a0d5aa58d232762e91915ef099c2e6091be573bc289cb9785cd04e6b9310825bf4ee735ff133a58f5db43986857e9796ce29a20b5248e12ad6520c2946d4d4d9d14cb9e8d34d29cdf194ee68132e3aed6ffd79b973031b1cf8e28b1bd8b871c386e1207cdca211a73dae74a5046db12dba2a293a892c393f2bf4518b7ed63b28a5e3280d19f9a0451b2ada94ca2c1726592df8984532b3562ee16a2a1fb2a83b52ba4bb02c132eb8c60939934b27166d5aa96cf59c6745660c16ed857dd9644267fe787dbca26d11a927df4cf9c5bdcce1c315bd8e79c4ccabf89339ade843a9dc301bc937bf6cc3072bda9013f534f7842841a4010d53c3c639d3553431490b97c34f3d7ca8221d3c8bfea9685e62ac763cc96b92ff4045a2214eb6c9dcf714bdc4ea8b713e870a9931459bc742ce97f4a8b60a7e94e26ccd6ecbd565e6ae6961472c5e8647b5d8f5c6cb400636c038cdc10729ec0bf2318a76638a117f919df3d50f51b4e193ac16cf79d9510e1c32c2c041b03f42d19bf691d9ec9ba2f90728907d3988899f441f9f689254cda52c5648bad787279af394db39ee56838f4e342acd3f72903d96318413ede8539da39a481adb29e163139d4e252b6f0853915fe9da87269a1833255d2a48cb29cb33d63e32d169f6a0947e8d291a1f98e82f2f7fc67c2d191afdb8446b95e2e58d680a8a25ba9233f5f8e9bea94278031f95e8439c97ac48f9826c89124d2a3f517d4ca22d4f3229a1e923c2a7201b242044f890449f72a95ef2cdc9795bd4c14724faf64f21b35450416b902e0f3e20d1a60efd17776257ac2430dc80f0f1883607cdd8a363b24dccb9830f47f49661592e7c8c184eff68441bf2ccb54ac8aa9f221f8c6833c69cc9334a346df2223a8db9c7e364ecaf9a1f8ae87745445159bb3b86dc1a3e12d18b342bcd98f31d218488c6e4dcb7745039447f12a3a7998a7e18a2bf48d94ac80e92b34a85e8d2829cfc393b64f39f107dccb0e79e7336c81c1a441f7573265d29985cddbce043109d90983926179525af29109de9606e318f9accfc00a2ff949fea9b3d7436918f3ff05529626758b8248f222757dcccf1c30f4d12aa5ce63f9bce531f3aeb90ed9c826a0b1d3ef41a83dc3cdfd9439bccf448d2ea4f39a387fe93b4b8162b8bdc3c0fcd75198440a8436569200e0703421445410c829082593ad31448202044268c45c24890a6b1283f13c040ca22b16820100683814040140885612004411086004110846118048128d8646307e92ce9f194dcc9cb010625805f5f96711d24af1096831684a7d28544e60c89c01534a059dfb18c98f0a906dc82e62c226b0088962c39a96e5360c613e94efbf0b8f1428201050a3df554fc417163ec23b925b0f4f166b7f0c78b52d113f61815ff48ddc54076d7262bc8be9f5e14fa1e0f554730ff008a7edfee44340683dd5530976046b2a6f8924306a6e62810a4189728c21b4d7420d06402245acd3e6f5553e79b8cfae86d6c518cd8e4f492989498d5152e7ea291940ae0d8f35fb03ce13c36d6a526480ec179bee14074b6fb5a0e65fe24d5f0509d3d674e57310efc44da9efaf1497979b58364c8c25e5394a545a2f47b2a6a9cf264acce374d25c07480ac94d90b67f35897b12a42d41b735858975f79afbc530ab30d1a51d4166e00c9defe14460dc6ed4cdae48d5c74a77c65e306c5b7225182cc009600990549319368519ee3306249692ca21bdda58dbe6e570e4542f4a03f40b7b138f9190cea6c06450035677c1f104027e4cf0ce7b0b4021fa0df4377f775cb50cf6a775e2ac3e82497202970ab3b96cb2eb6f594dea3fcf7de4a86eb90e7c610280fd6a8fc6fede9eba609ff17677749a18901f3a548e2111e9fa22cd080698293ada8d205fab063b1c66dd4416c8b2c2ba658e01730f7d56ac9bb2b6c293a0a356ba02d9531ccc3614b0cb814bcae51cd948d2cea5642fa98aa6998d72040e1fca06285b3e4c445cdc3c8b0a2e9c6ea66b7ccdf7f13cf6b4e44c8f0237e2dfff4f1672addc59abd1d695bbc39e3ce220d69fd65db26171208ca86ade8f21554680fc805405bc6c7697426363dd8f57c2d0a1391f3b6e251fcaef060d850a81c592b9851cebd71e73659f06fdc9ab74bd13d89a6a29a69b8c5343bbd2da4eed62dd43cfcf441c806e824b3a352ac6243844c5b7773d7321c3a4f558fd50fe18300aecd7151096c51a98319ffb43f9377d0b04f5b4b815f4e942cb98c16445382409d90191a76a07ce177fe58cdbecf17fb91a20feac5d3ba485ab299e9f66355c637cacabecb8c2c327e5e293bb58f7e12b93c146bc25467504b1e1a994a0e25a8d868833d3f5464eb7fe81f49adbe4f5554a7b2578d9b3900876121e99b89a93000ceaae75effff74092c953d2d29de3c97b435557011c6cac2645a0003ffd3c9cd43032f8a1076471f8145094031c6ea2a2187e3550e4eb22574da87db4845e79eabd2cf37d48d3201f759cdadb18806ef7a7b0ecafff306d11ff511a2d2c0190105721282a1acb05e8b6e059285d85f0057364eb110bc739d543e1380d382f7a7fbe552a96cc86a33e6afe66a78f25cad6be8683b6e12e5eb1ca4895c04e85a3c55027ae31819d28e829cdcbbc5ae227341a803ffd04d3952e5b0e51e3024e780adb386b7d9a069cc2291a9dd687100a510e75ae610f1f88905d7407ee610e2b03270b04b5b48fa7488534a46806e82060bdadc9fbe3da74698125ae6190eaad2b248a2bf7b80437bfd86af2a3e4fbec81ebd031b2d24f261bdc388ab1d7b9d0ec8dfc10dd9d90cf7a125289deb2aca54e79294db2ababeab83989a78e929725b95185cf79ec39a644bab5eea06be8fd20fd55c73f5b007dab0af0baaa20367587054200dfe600d528093b8d96c0816159056e6bcc73deea0db1a2648ced90afc756e38eba9b6e5eb64b315c232ccf25e74af085ee43c88372a7a282115565605e1a6031d7dff65673485c6753595aa631f7dde8d573ef340bd08a942d1f831f9140dab14771ffa789bc59d2b03b14dab9d8232fea041a3d031237ebecae2970b8338fe12eb84397eba038f7cf2a6573deba8bbcdb03e0ab0aa97b4012ba20dcd06ac205a9399c738d1a98ed97ff1ea8eaf77cc639b4533ca91aff47dee19fba0aa84ea7d120b33b5a6f09df3c5619220746d1347be85ed88d18d6e84957dacf0cc84754c7df275d73adb18fd2fc6d0b28b9e61254a39beea9fc2ad0d42e2a9c470ca5e7bc82c04f992a14e77e4cdf749c3223d8f8b75d6837f113a61980e0e6aa32f1a73a2b21bdebdea48179ff517619da2dd1461538abc54956996537e57366d401769678e3f5a801e73ee18e750de7a5b5b53ddc9c6a9cd86c8649bccaf05644ea56cda2731614d6e4a3ec8b3ea06fbb8b17ea3ce2d9627f6a90f315225e390a5ee47a545e65913437bbe3e45819c40726e66b7eab8bb3afac2a35eeb45c62577121b6639b19fdcef2366ee75ff0dcc710b53366437eb6da30c0c4d8704b98bc122d6ef6f64ddb3c31a7a39d41021ca35955d8c217614a7283959c27f873d521a7d7bc51bdb6135add6149a9d06481fd585684c989e5a97804154be7d6b3ab32199ee83473dd5a62c9df9e02f42b7a0b83037b753125be10c37adf1669bdb68231bdad0c68e808ac9bf9fd999c8291729f1ef6ad88b31b45d3a78792fe2b7017b7130153202c22056ded93417a95c2ec0880c4fd107e27890cd8c284fb6273bad3f259f5be61cbfd6d729a6e376f8e50edef92a392c40dc7dd8ae63b24165d02f95e999295e99266912966b56a0f3c1e7fa935922d9207c722844178147fd27c207864c5af95efb349910121c04e437205b1124e14bbee51e83e1d33262abc61f9327ec2639769750aa328027e0b3fb75f17cd3527958275b166a93aaa0128b57389831c278e80c8a5e41ca937a15f49837e0f4c47370b86276ae3df29961f7666f157f35ac9166cd5d6ba02b8d65c66ab7d2397440560aaba100884c9bb2f9ada1196648af891ddbe4d44a3779a66cd60598f2e2621cca0b6067a1ae62a27a364c871699501900a22fae20595275dbaeff8e991b23fc1fbb50ea55c521125a0ac33d113efb0f51e74f535ee45d2ee12990989ed16e7ea6f33e0c83438adbe8f92071aca6cee27427571b2ed1b8dc7157c61540e43d21ee612ae1128b7fe46e77e546850f8a4cb379b0ae738351b49a5981618d16f97023424f1daf838ae46948c25ba570280bd61208efba1fa6d247480ae6524eb2f758c81155d0dce9473424889771fd7512abab78c721e7d18d3a093fb89bc75f292bb069cbfff5d4b15860318e6202b195aac70279ac5c4cc8bbe618ea94f132c5631eacbbec60c6d614787c6790b42145cca9df41e77780e779bb8d6cb03be6b96711eb69b6bc8f426ac8ed78970e7a92a21d7734e788c0dbdd183bc237627081d32ef1ea783a0df39ffd640f1865c57f7cea4816b9a112962452ea3f41b28202e241f1a7850a8cb97191f7d05b514791d50e0812cbfe8c7ad47d028bb6d4caf71a31f16bc88406a5dc3c5e493c4315154960bba8c74904717cdf3e67d042fc9809e08673583545038cbe0145154bea412539997ee90521397c815bb0368bafedae27ada3fe0dab2cd55c4f15f525d7d683604a48f8bd304a64b979d64c13a89a374b7f9d3471e2392b69c68f97374feeaf834b109b19c2f053865bb61c4e798f895eb837d2a3008ed87c1041441a01d01e70b895db390c71018114c6a2f214924cce01d198656dea66b91bf70ee60c28a2df321083e48da59d4736a5a59a80846e6e325879676953f774f423edb58143271cd9cdd322dcb109c17213ea5f06a5072e06051015a95f418c67bf9f88d1fda3c2ff8b94d1a54e44c7704f05dca464be3d5293a24393758de3fce457e3d0361c72f19c0ff11b095cc163dab66708cfb00b624e0c6c9c26eb4e0a803106bc906a14da3d2660bbef19b2d5a986c708ba9c854270b12ee3d5b7fcfca9de61443517eac87f491da3f07a9cccc027ec2d7e4d00c252541f246a6f73dabff78f7cd5e0d5efac3f6eb0a707c4eb9c2bec00ea1f9fd4a2e7429acad4204120cb98537ba721fada5e350f4d7e4fbafc5771ec0cc0b8dd93c9d42d6958c40d93f9427c50f8d3849c7cbba3649a235e943e82dacc0a3fca5af88f0d6e75ae26caf4124ad1e015167387042bb95657f6cf6604c6d4e87044949f09e87d6db400b5d0baa80e9d9a61082748c250c092623b7a797b68a7ba27800ce4432d405b1857b9f52e0bc8404d8fa947b702a2539280ed01670628e1ffeb940c38f5ba7ec99cb14279e8e66686fbb29f430bd3aede9daa6811814e89baa9100ea5c2389a51ac61c1c8936f256b4f1b1dd722226c5c9c4443eaca7ab6113204f177213ab47a0d07b2ee7e6e0d42", - "0x3a65787472696e7369635f696e646578": "0x00000000", - "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", - "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", - "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", - "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", - "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0500", - "0x5f9cc45b7a00c5899361e1c6099678dc5e0621c4869aa60c02be9adcc98a0d1d": "0x0888dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0100000000000000d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae690100000000000000", - "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", - "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", - "0x658faa385070e074c85bf6b568cf055506d22dc781f44e506e51707fab5eea4d0300": "0xff7f", - "0x658faa385070e074c85bf6b568cf05550e30450fc4d507a846032a7fa65d9a430000": "0x01", - "0x658faa385070e074c85bf6b568cf05550e30450fc4d507a846032a7fa65d9a430300": "0x01", - "0x658faa385070e074c85bf6b568cf05552fd68e6f37598f679d0698930b5bbb470300": "0x0000", - "0x658faa385070e074c85bf6b568cf05554e7b9012096b41c4eb3aaf947f6ea429": "0x0600", - "0x658faa385070e074c85bf6b568cf05554efd2c1e9753037696296e2bfa4460950300": "0x0000000000000000", - "0x658faa385070e074c85bf6b568cf055557c875e4cff74148e4628f264b974c80": "0x0000000000000000", - "0x658faa385070e074c85bf6b568cf05555cd1c97edf92be296fb8ae73ee8611260000": "0x0000", - "0x658faa385070e074c85bf6b568cf05555cd1c97edf92be296fb8ae73ee8611260300": "0x0004", - "0x658faa385070e074c85bf6b568cf05555f3bb7bcd0a076a48abf8c256d221721": "0x0200", - "0x658faa385070e074c85bf6b568cf055564b6168414916325e7cb4f3f47691e110300": "0x0000", - "0x658faa385070e074c85bf6b568cf05556dcf6d297802ab84a1c68cb9453399920300": "0x0000", - "0x658faa385070e074c85bf6b568cf0555741b883d2519eed91857993bfd4df0ba0000": "0x4000", - "0x658faa385070e074c85bf6b568cf05557641384bb339f3758acddfd7053d33170000": "0x6400", - "0x658faa385070e074c85bf6b568cf05557641384bb339f3758acddfd7053d33170300": "0x6300", - "0x658faa385070e074c85bf6b568cf05557d15dd66fbf0cbda1d3a651b5e606df20300": "0x8096980000000000", - "0x658faa385070e074c85bf6b568cf055586cea6ddbfb037714c1e679cc83298a70000": "0x0100", - "0x658faa385070e074c85bf6b568cf0555919db2fe18203eba898cee471ef192400000": "0xffff", - "0x658faa385070e074c85bf6b568cf0555919db2fe18203eba898cee471ef192400300": "0xe803", - "0x658faa385070e074c85bf6b568cf0555a1048e9d244171852dfe8db314dc68ca0000": "0x0000", - "0x658faa385070e074c85bf6b568cf0555a1048e9d244171852dfe8db314dc68ca0300": "0x0000", - "0x658faa385070e074c85bf6b568cf0555b6522cfe03433e9e101a258ee2f580ab0300": "0x0010", - "0x658faa385070e074c85bf6b568cf0555c57fc7240b4e0c444a010d7fe83ec3ec0300": "0x8813", - "0x658faa385070e074c85bf6b568cf0555d5fe74da02c7b4bbb340fb368eee3e770000": "0x01", - "0x658faa385070e074c85bf6b568cf0555fabe6b131d9fa6e6d6cacbe7586c3b8a0000": "0x4000", - "0x658faa385070e074c85bf6b568cf0555fabe6b131d9fa6e6d6cacbe7586c3b8a0300": "0x0010", - "0x658faa385070e074c85bf6b568cf0555ffabb584688c82a9b01a0527f0afd3db0300": "0x0000", - "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0x84b82a4594e531d95ee4af12f83baea04e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x84b82a4594e531d95ee4af12f83baea0ba7fb8745735dc3be2a2c61a72c39e78": "0x0c8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", - "0x8a493ef65ff3987a1fbc9979200ad1af4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x8bcc11b860d2b04ed6a8e9e0075d4ba34e7b9012096b41c4eb3aaf947f6ea429": "0x0400", - "0x8bcc11b860d2b04ed6a8e9e0075d4ba3ba7fb8745735dc3be2a2c61a72c39e78": "0x0c1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", - "0xb8c7f96c134ebb49eb7e77df71f098ad4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xbd2a529379475088d3e29a918cd478724e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00dc4eb686b8e00d", - "0xca407206ec1ab726b2636c4b145ac2874e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", - "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" - }, - "childrenDefault": {} - } - } -} \ No newline at end of file From 87c8d5f8a663b397183d327a87bdabfb50eab662 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 2 Jul 2024 22:49:33 +0400 Subject: [PATCH 022/185] chore: bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 7f9c1c943..1d01c0b92 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 153, + spec_version: 154, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 60d0d7df323242e7679faf857e0cfdca105c22eb Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 15:25:10 +0400 Subject: [PATCH 023/185] refactor: hotkey swap + tests --- pallets/subtensor/src/lib.rs | 13 +- pallets/subtensor/src/registration.rs | 139 +-- pallets/subtensor/src/swap.rs | 438 ++++++++++ pallets/subtensor/tests/mock.rs | 6 +- pallets/subtensor/tests/swap.rs | 1133 +++++++++++++++++++++++++ 5 files changed, 1583 insertions(+), 146 deletions(-) create mode 100644 pallets/subtensor/src/swap.rs create mode 100644 pallets/subtensor/tests/swap.rs diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f50d0c722..049a52082 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -43,6 +43,7 @@ mod registration; mod root; mod serving; mod staking; +mod swap; mod uids; mod utils; mod weights; @@ -740,7 +741,7 @@ pub mod pallet { pub(super) type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; #[pallet::storage] // --- MAP ( key ) --> last_block - pub(super) type LastTxBlock = + pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::storage] // --- MAP ( key ) --> last_block pub(super) type LastTxBlockDelegateTake = @@ -756,10 +757,10 @@ pub mod pallet { pub type ServingRateLimit = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultServingRateLimit>; #[pallet::storage] // --- MAP ( netuid, hotkey ) --> axon_info - pub(super) type Axons = + pub type Axons = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; #[pallet::storage] // --- MAP ( netuid, hotkey ) --> prometheus_info - pub(super) type Prometheus = StorageDoubleMap< + pub type Prometheus = StorageDoubleMap< _, Identity, u16, @@ -1013,13 +1014,13 @@ pub mod pallet { } #[pallet::storage] // --- DMAP ( netuid, hotkey ) --> uid - pub(super) type Uids = + pub type Uids = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; #[pallet::storage] // --- DMAP ( netuid, uid ) --> hotkey - pub(super) type Keys = + pub type Keys = StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; #[pallet::storage] // --- DMAP ( netuid ) --> (hotkey, se, ve) - pub(super) type LoadedEmission = + pub type LoadedEmission = StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; #[pallet::storage] // --- DMAP ( netuid ) --> active diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index dda00db54..da1d84183 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -1,6 +1,5 @@ use super::*; -use frame_support::storage::IterableStorageDoubleMap; -use sp_core::{Get, H256, U256}; +use sp_core::{H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; use sp_runtime::Saturating; use system::pallet_prelude::BlockNumberFor; @@ -591,140 +590,4 @@ impl Pallet { let vec_work: Vec = Self::hash_to_vec(work); (nonce, vec_work) } - - pub fn do_swap_hotkey( - origin: T::RuntimeOrigin, - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - ) -> DispatchResultWithPostInfo { - let coldkey = ensure_signed(origin)?; - - let mut weight = T::DbWeight::get().reads_writes(2, 0); - ensure!( - Self::coldkey_owns_hotkey(&coldkey, old_hotkey), - Error::::NonAssociatedColdKey - ); - - let block: u64 = Self::get_current_block_as_u64(); - ensure!( - !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), - Error::::HotKeySetTxRateLimitExceeded - ); - - weight.saturating_accrue(T::DbWeight::get().reads(2)); - - ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - - weight.saturating_accrue( - T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1)) as u64), - ); - - let swap_cost = 1_000_000_000u64; - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), - Error::::NotEnoughBalanceToPaySwapHotKey - ); - let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - Self::burn_tokens(actual_burn_amount); - - Owner::::remove(old_hotkey); - Owner::::insert(new_hotkey, coldkey.clone()); - weight.saturating_accrue(T::DbWeight::get().writes(2)); - - if let Ok(total_hotkey_stake) = TotalHotkeyStake::::try_get(old_hotkey) { - TotalHotkeyStake::::remove(old_hotkey); - TotalHotkeyStake::::insert(new_hotkey, total_hotkey_stake); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - - if let Ok(delegate_take) = Delegates::::try_get(old_hotkey) { - Delegates::::remove(old_hotkey); - Delegates::::insert(new_hotkey, delegate_take); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - - if let Ok(last_tx) = LastTxBlock::::try_get(old_hotkey) { - LastTxBlock::::remove(old_hotkey); - LastTxBlock::::insert(new_hotkey, last_tx); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - - let mut coldkey_stake: Vec<(T::AccountId, u64)> = vec![]; - for (coldkey, stake_amount) in Stake::::iter_prefix(old_hotkey) { - coldkey_stake.push((coldkey.clone(), stake_amount)); - } - - let _ = Stake::::clear_prefix(old_hotkey, coldkey_stake.len() as u32, None); - weight.saturating_accrue(T::DbWeight::get().writes(coldkey_stake.len() as u64)); - - for (coldkey, stake_amount) in coldkey_stake { - Stake::::insert(new_hotkey, coldkey, stake_amount); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - let mut netuid_is_member: Vec = vec![]; - for netuid in as IterableStorageDoubleMap>::iter_key_prefix(old_hotkey) { - netuid_is_member.push(netuid); - } - - let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); - weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); - - for netuid in netuid_is_member.iter() { - IsNetworkMember::::insert(new_hotkey, netuid, true); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - for netuid in netuid_is_member.iter() { - if let Ok(axon_info) = Axons::::try_get(netuid, old_hotkey) { - Axons::::remove(netuid, old_hotkey); - Axons::::insert(netuid, new_hotkey, axon_info); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - } - - for netuid in netuid_is_member.iter() { - if let Ok(uid) = Uids::::try_get(netuid, old_hotkey) { - Uids::::remove(netuid, old_hotkey); - Uids::::insert(netuid, new_hotkey, uid); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - - Keys::::insert(netuid, uid, new_hotkey); - - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - LoadedEmission::::mutate(netuid, |emission_exists| match emission_exists { - Some(emissions) => { - if let Some(emission) = emissions.get_mut(uid as usize) { - let (_, se, ve) = emission; - *emission = (new_hotkey.clone(), *se, *ve); - } - } - None => {} - }); - - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - } - - Self::set_last_tx_block(&coldkey, block); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - Self::deposit_event(Event::HotkeySwapped { - coldkey, - old_hotkey: old_hotkey.clone(), - new_hotkey: new_hotkey.clone(), - }); - - Ok(Some(weight).into()) - } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs new file mode 100644 index 000000000..4d7aaf6f7 --- /dev/null +++ b/pallets/subtensor/src/swap.rs @@ -0,0 +1,438 @@ +use super::*; +use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use sp_core::Get; + +impl Pallet { + /// Swaps the hotkey of a coldkey account. + /// + /// # Arguments + /// + /// * `origin` - The origin of the transaction. + /// * `old_hotkey` - The old hotkey to be swapped. + /// * `new_hotkey` - The new hotkey to replace the old one. + /// + /// # Returns + /// + /// * `DispatchResultWithPostInfo` - The result of the dispatch. + /// + /// # Errors + /// + /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. + /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. + /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. + /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. + /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. + pub fn do_swap_hotkey( + origin: T::RuntimeOrigin, + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + ) -> DispatchResultWithPostInfo { + let coldkey = ensure_signed(origin)?; + + let mut weight = T::DbWeight::get().reads_writes(2, 0); + ensure!( + Self::coldkey_owns_hotkey(&coldkey, old_hotkey), + Error::::NonAssociatedColdKey + ); + + let block: u64 = Self::get_current_block_as_u64(); + ensure!( + !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), + Error::::HotKeySetTxRateLimitExceeded + ); + + weight.saturating_accrue(T::DbWeight::get().reads(2)); + + ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + weight + .saturating_accrue(T::DbWeight::get().reads((TotalNetworks::::get() + 1u16) as u64)); + + let swap_cost = 1_000_000_000u64; + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + + Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight)?; + Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight)?; + Self::swap_delegates(old_hotkey, new_hotkey, &mut weight)?; + Self::swap_last_tx_block(old_hotkey, new_hotkey, &mut weight)?; + Self::swap_stake(old_hotkey, new_hotkey, &mut weight)?; + + // Store the value of is_network_member for the old key + let netuid_is_member: Vec = Self::get_netuid_is_member(old_hotkey, &mut weight)?; + + Self::swap_is_network_member(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + Self::swap_axons(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + Self::swap_keys(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + + Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight)?; + + Self::set_last_tx_block(&coldkey, block); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + Self::deposit_event(Event::HotkeySwapped { + coldkey, + old_hotkey: old_hotkey.clone(), + new_hotkey: new_hotkey.clone(), + }); + + Ok(Some(weight).into()) + } + + /// Retrieves the network membership status for a given hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The hotkey to check for network membership. + /// + /// # Returns + /// + /// * `Result, Error>` - A vector of network IDs where the hotkey is a member. + pub fn get_netuid_is_member( + old_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> Result, Error> { + let netuid_is_member: Vec = + as IterableStorageDoubleMap<_, _, _>>::iter_prefix(old_hotkey) + .map(|(netuid, _)| netuid) + .collect(); + weight.saturating_accrue(T::DbWeight::get().reads(netuid_is_member.len() as u64)); + Ok(netuid_is_member) + } + + /// Swaps the owner of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `coldkey` - The coldkey owning the hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_owner( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + coldkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + Owner::::remove(old_hotkey); + Owner::::insert(new_hotkey, coldkey.clone()); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + Ok(()) + } + + /// Swaps the total stake of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_total_hotkey_stake( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + if let Ok(total_hotkey_stake) = TotalHotkeyStake::::try_get(old_hotkey) { + TotalHotkeyStake::::remove(old_hotkey); + TotalHotkeyStake::::insert(new_hotkey, total_hotkey_stake); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + Ok(()) + } + + /// Swaps the delegates of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_delegates( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + if let Ok(delegate_take) = Delegates::::try_get(old_hotkey) { + Delegates::::remove(old_hotkey); + Delegates::::insert(new_hotkey, delegate_take); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + Ok(()) + } + + /// Swaps the last transaction block of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_last_tx_block( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + if let Ok(last_tx) = LastTxBlock::::try_get(old_hotkey) { + LastTxBlock::::remove(old_hotkey); + LastTxBlock::::insert(new_hotkey, last_tx); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + Ok(()) + } + + /// Swaps the stake of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_stake( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + let mut writes = 0; + let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); + for (coldkey, stake_amount) in stakes { + Stake::::insert(new_hotkey, &coldkey, stake_amount); + Stake::::remove(old_hotkey, &coldkey); + writes += 2; // One write for insert and one for remove + } + *weight += T::DbWeight::get().writes(writes as u64); + Ok(()) + } + /// Swaps the network membership status of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_is_network_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &Vec, + weight: &mut Weight, + ) -> DispatchResult { + let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); + weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); + for netuid in netuid_is_member.iter() { + IsNetworkMember::::insert(new_hotkey, netuid, true); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + Ok(()) + } + + /// Swaps the axons of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_axons( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &Vec, + weight: &mut Weight, + ) -> DispatchResult { + for netuid in netuid_is_member.iter() { + if let Ok(axon_info) = Axons::::try_get(netuid, old_hotkey) { + Axons::::remove(netuid, old_hotkey); + Axons::::insert(netuid, new_hotkey, axon_info); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + } + Ok(()) + } + /// Swaps the references in the keys storage map of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_keys( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &Vec, + weight: &mut Weight, + ) -> DispatchResult { + let mut writes = 0; + for netuid in netuid_is_member { + let keys: Vec<(u16, T::AccountId)> = Keys::::iter_prefix(netuid).collect(); + for (uid, key) in keys { + if key == *old_hotkey { + log::info!("old hotkey found: {:?}", old_hotkey); + Keys::::insert(netuid, uid, new_hotkey.clone()); + } + writes += 2; + } + } + log::info!("writes: {:?}", writes); + *weight += T::DbWeight::get().writes(writes as u64); + Ok(()) + } + + /// Swaps the loaded emission of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_loaded_emission( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) -> DispatchResult { + for netuid in netuid_is_member { + if let Some(mut emissions) = LoadedEmission::::get(netuid) { + for emission in emissions.iter_mut() { + if emission.0 == *old_hotkey { + emission.0 = new_hotkey.clone(); + } + } + LoadedEmission::::insert(netuid, emissions); + } + } + *weight += T::DbWeight::get().writes(netuid_is_member.len() as u64); + Ok(()) + } + + /// Swaps the UIDs of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_uids( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &Vec, + weight: &mut Weight, + ) -> DispatchResult { + for netuid in netuid_is_member.iter() { + if let Ok(uid) = Uids::::try_get(netuid, old_hotkey) { + Uids::::remove(netuid, old_hotkey); + Uids::::insert(netuid, new_hotkey, uid); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + } + Ok(()) + } + + /// Swaps the Prometheus data of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_prometheus( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &Vec, + weight: &mut Weight, + ) -> DispatchResult { + for netuid in netuid_is_member.iter() { + if let Ok(prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { + Prometheus::::remove(netuid, old_hotkey); + Prometheus::::insert(netuid, new_hotkey, prometheus_info); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + } + Ok(()) + } + + /// Swaps the total hotkey-coldkey stakes for the current interval. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Returns + /// + /// * `Result<(), Error>` - The result of the operation. + pub fn swap_total_hotkey_coldkey_stakes_this_interval( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + let stakes: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); + log::info!("Stakes to swap: {:?}", stakes); + for (coldkey, stake) in stakes { + log::info!("Swapping stake for coldkey: {:?}, stake: {:?}", coldkey, stake); + TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake); + TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); + weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove + } + Ok(()) + } +} diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index f2eeddb3f..195063899 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -1,5 +1,7 @@ #![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] - +use frame_support::derive_impl; +use frame_support::dispatch::DispatchResultWithPostInfo; +use frame_support::weights::constants::RocksDbWeight; use frame_support::{ assert_ok, derive_impl, dispatch::DispatchResultWithPostInfo, @@ -88,7 +90,7 @@ impl system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); type BlockLength = (); - type DbWeight = (); + type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type Hash = H256; diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs new file mode 100644 index 000000000..d6052c8fc --- /dev/null +++ b/pallets/subtensor/tests/swap.rs @@ -0,0 +1,1133 @@ +use codec::Encode; +use frame_support::weights::Weight; +use frame_support::{assert_err, assert_ok}; +use frame_system::Config; +mod mock; +use mock::*; +use pallet_subtensor::*; +use sp_core::U256; + +#[test] +fn test_do_swap_hotkey_ok() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64; + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey + )); + + // Verify the swap + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), + coldkey + ); + assert_ne!( + SubtensorModule::get_owning_coldkey_for_hotkey(&old_hotkey), + coldkey + ); + + // Verify other storage changes + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + SubtensorModule::get_total_stake_for_hotkey(&old_hotkey) + ); + assert_eq!( + SubtensorModule::get_delegate(new_hotkey.encode()), + SubtensorModule::get_delegate(old_hotkey.encode()) + ); + assert_eq!( + SubtensorModule::get_last_tx_block(&new_hotkey), + SubtensorModule::get_last_tx_block(&old_hotkey) + ); + + // Verify raw storage maps + // Stake + for (coldkey, stake_amount) in Stake::::iter_prefix(&old_hotkey) { + assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); + } + + let mut weight = Weight::zero(); + // UIDs + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + assert_eq!( + Uids::::get(netuid, &new_hotkey), + Uids::::get(netuid, &old_hotkey) + ); + } + + // Prometheus + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + assert_eq!( + Prometheus::::get(netuid, &new_hotkey), + Prometheus::::get(netuid, &old_hotkey) + ); + } + + // LoadedEmission + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + assert_eq!( + LoadedEmission::::get(netuid).unwrap(), + LoadedEmission::::get(netuid).unwrap() + ); + } + + // IsNetworkMember + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + assert!(IsNetworkMember::::contains_key(&new_hotkey, netuid)); + assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); + } + + // Owner + assert_eq!(Owner::::get(&new_hotkey), coldkey); + + // TotalHotkeyStake + assert_eq!( + TotalHotkeyStake::::get(&new_hotkey), + TotalHotkeyStake::::get(&old_hotkey) + ); + + // Delegates + assert_eq!( + Delegates::::get(&new_hotkey), + Delegates::::get(&old_hotkey) + ); + + // LastTxBlock + assert_eq!( + LastTxBlock::::get(&new_hotkey), + LastTxBlock::::get(&old_hotkey) + ); + + // Axons + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + assert_eq!( + Axons::::get(netuid, &new_hotkey), + Axons::::get(netuid, &old_hotkey) + ); + } + + // TotalHotkeyColdkeyStakesThisInterval + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), + TotalHotkeyColdkeyStakesThisInterval::::get(&old_hotkey, &coldkey) + ); + }); +} + +#[test] +fn test_do_swap_hotkey_ok_robust() { + new_test_ext(1).execute_with(|| { + let num_subnets: u16 = 10; + let tempo: u16 = 13; + let swap_cost = 1_000_000_000u64; + + // Create 10 sets of keys + let mut old_hotkeys = vec![]; + let mut new_hotkeys = vec![]; + let mut coldkeys = vec![]; + + for i in 0..10 { + old_hotkeys.push(U256::from(i * 2 + 1)); + new_hotkeys.push(U256::from(i * 2 + 2)); + coldkeys.push(U256::from(i * 2 + 11)); + } + + + // Setup initial state + for netuid in 1..=num_subnets { + add_network(netuid, tempo, 0); + SubtensorModule::set_max_registrations_per_block(netuid, 20); + SubtensorModule::set_target_registrations_per_interval(netuid, 1000); + log::info!("Registrations this interval for netuid {:?} is {:?}", netuid, SubtensorModule::get_target_registrations_per_interval(netuid)); + for i in 0..10 { + register_ok_neuron(netuid, old_hotkeys[i], coldkeys[i], 0); + } + } + + // Add balance to coldkeys for swap cost + for i in 0..10 { + SubtensorModule::add_balance_to_coldkey_account(&coldkeys[i], swap_cost); + } + + // Perform the swaps for only two hotkeys + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkeys[0]), + &old_hotkeys[0], + &new_hotkeys[0] + )); + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkeys[1]), + &old_hotkeys[1], + &new_hotkeys[1] + )); + + // Verify the swaps + for netuid in 1..=num_subnets { + for i in 0..10 { + if i == 0 || i == 1 { + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkeys[i]), + coldkeys[i] + ); + assert_ne!( + SubtensorModule::get_owning_coldkey_for_hotkey(&old_hotkeys[i]), + coldkeys[i] + ); + + // Verify other storage changes + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkeys[i]), + SubtensorModule::get_total_stake_for_hotkey(&old_hotkeys[i]) + ); + + assert_eq!( + SubtensorModule::get_delegate(new_hotkeys[i].encode()), + SubtensorModule::get_delegate(old_hotkeys[i].encode()) + ); + + assert_eq!( + SubtensorModule::get_last_tx_block(&new_hotkeys[i]), + SubtensorModule::get_last_tx_block(&old_hotkeys[i]) + ); + + // Verify raw storage maps + // Stake + for (coldkey, stake_amount) in Stake::::iter_prefix(&old_hotkeys[i]) { + assert_eq!(Stake::::get(&new_hotkeys[i], &coldkey), stake_amount); + } + + let mut weight = Weight::zero(); + // UIDs + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + assert_eq!( + Uids::::get(netuid, &new_hotkeys[i]), + Uids::::get(netuid, &old_hotkeys[i]) + ); + } + + // Prometheus + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + assert_eq!( + Prometheus::::get(netuid, &new_hotkeys[i]), + Prometheus::::get(netuid, &old_hotkeys[i]) + ); + } + + // LoadedEmission + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + assert_eq!( + LoadedEmission::::get(netuid).unwrap(), + LoadedEmission::::get(netuid).unwrap() + ); + } + + // IsNetworkMember + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + assert!(IsNetworkMember::::contains_key(&new_hotkeys[i], netuid)); + assert!(!IsNetworkMember::::contains_key(&old_hotkeys[i], netuid)); + } + + // Owner + assert_eq!(Owner::::get(&new_hotkeys[i]), coldkeys[i]); + } else { + // Ensure other hotkeys remain unchanged + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&old_hotkeys[i]), + coldkeys[i] + ); + assert_ne!( + SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkeys[i]), + coldkeys[i] + ); + } + } + } + }); +} + +#[test] +fn test_do_swap_hotkey_err_not_owner() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let not_owner_coldkey = U256::from(4); + let swap_cost = 1_000_000_000u64; + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(¬_owner_coldkey, swap_cost); + + // Attempt the swap with a non-owner coldkey + assert_err!( + SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(not_owner_coldkey), + &old_hotkey, + &new_hotkey + ), + Error::::NonAssociatedColdKey + ); + }); +} + +#[test] +fn test_swap_owner_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Initialize Owner for old_hotkey + Owner::::insert(&old_hotkey, &coldkey); + + // Perform the swap + assert_ok!(SubtensorModule::swap_owner( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); + + // Verify the swap + assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(&old_hotkey)); + }); +} + +#[test] +fn test_swap_owner_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Ensure old_hotkey does not exist + assert!(!Owner::::contains_key(&old_hotkey)); + + // Perform the swap + assert_ok!(SubtensorModule::swap_owner( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); + + // Verify the swap + assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(&old_hotkey)); + }); +} + +#[test] +fn test_swap_owner_new_hotkey_already_exists() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let another_coldkey = U256::from(4); + let mut weight = Weight::zero(); + + // Initialize Owner for old_hotkey and new_hotkey + Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(&new_hotkey, &another_coldkey); + + // Perform the swap + assert_ok!(SubtensorModule::swap_owner( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); + + // Verify the swap + assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(&old_hotkey)); + }); +} + +#[test] +fn test_swap_owner_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Initialize Owner for old_hotkey + Owner::::insert(&old_hotkey, &coldkey); + + // Perform the swap + assert_ok!(SubtensorModule::swap_owner( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_total_hotkey_stake_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let total_stake = 1000u64; + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyStake for old_hotkey + TotalHotkeyStake::::insert(&old_hotkey, total_stake); + + // Perform the swap + assert_ok!(SubtensorModule::swap_total_hotkey_stake( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the swap + assert_eq!(TotalHotkeyStake::::get(&new_hotkey), total_stake); + assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + }); +} + +#[test] +fn test_swap_total_hotkey_stake_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let mut weight = Weight::zero(); + + // Ensure old_hotkey does not exist + assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + + // Perform the swap + assert_ok!(SubtensorModule::swap_total_hotkey_stake( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify that new_hotkey does not have a stake + assert!(!TotalHotkeyStake::::contains_key(&new_hotkey)); + }); +} + +#[test] +fn test_swap_total_hotkey_stake_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let total_stake = 1000u64; + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyStake for old_hotkey + TotalHotkeyStake::::insert(&old_hotkey, total_stake); + + // Perform the swap + assert_ok!(SubtensorModule::swap_total_hotkey_stake( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_delegates_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let delegate_take = 10u16; + let mut weight = Weight::zero(); + + // Initialize Delegates for old_hotkey + Delegates::::insert(&old_hotkey, delegate_take); + + // Perform the swap + assert_ok!(SubtensorModule::swap_delegates( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the swap + assert_eq!(Delegates::::get(&new_hotkey), delegate_take); + assert!(!Delegates::::contains_key(&old_hotkey)); + }); +} + +#[test] +fn test_swap_delegates_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let mut weight = Weight::zero(); + + // Ensure old_hotkey does not exist + assert!(!Delegates::::contains_key(&old_hotkey)); + + // Perform the swap + assert_ok!(SubtensorModule::swap_delegates( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify that new_hotkey does not have a delegate + assert!(!Delegates::::contains_key(&new_hotkey)); + }); +} + +#[test] +fn test_swap_delegates_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let delegate_take = 10u16; + let mut weight = Weight::zero(); + + // Initialize Delegates for old_hotkey + Delegates::::insert(&old_hotkey, delegate_take); + + // Perform the swap + assert_ok!(SubtensorModule::swap_delegates( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_last_tx_block_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let last_tx_block = 100u64; + let mut weight = Weight::zero(); + + // Initialize LastTxBlock for old_hotkey + LastTxBlock::::insert(&old_hotkey, last_tx_block); + + // Perform the swap + assert_ok!(SubtensorModule::swap_last_tx_block( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the swap + assert_eq!(LastTxBlock::::get(&new_hotkey), last_tx_block); + assert!(!LastTxBlock::::contains_key(&old_hotkey)); + }); +} + +#[test] +fn test_swap_last_tx_block_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let mut weight = Weight::zero(); + + // Ensure old_hotkey does not exist + assert!(!LastTxBlock::::contains_key(&old_hotkey)); + + // Perform the swap + assert_ok!(SubtensorModule::swap_last_tx_block( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify that new_hotkey does not have a last transaction block + assert!(!LastTxBlock::::contains_key(&new_hotkey)); + }); +} + +#[test] +fn test_swap_last_tx_block_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let last_tx_block = 100u64; + let mut weight = Weight::zero(); + + // Initialize LastTxBlock for old_hotkey + LastTxBlock::::insert(&old_hotkey, last_tx_block); + + // Perform the swap + assert_ok!(SubtensorModule::swap_last_tx_block( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_stake_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake_amount = 1000u64; + let mut weight = Weight::zero(); + + // Initialize Stake for old_hotkey + Stake::::insert(&old_hotkey, &coldkey, stake_amount); + + // Perform the swap + assert_ok!(SubtensorModule::swap_stake( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the swap + assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); + assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); + }); +} + +#[test] +fn test_swap_stake_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake_amount = 1000u64; + let mut weight = Weight::zero(); + + // Initialize Stake for old_hotkey + Stake::::insert(&old_hotkey, &coldkey, stake_amount); + + // Ensure old_hotkey has a stake + assert!(Stake::::contains_key(&old_hotkey, &coldkey)); + + // Perform the swap + assert_ok!(SubtensorModule::swap_stake( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify that new_hotkey has the stake and old_hotkey does not + assert!(Stake::::contains_key(&new_hotkey, &coldkey)); + assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); + }); +} + +#[test] +fn test_swap_stake_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake_amount = 1000u64; + let mut weight = Weight::zero(); + + // Initialize Stake for old_hotkey + Stake::::insert(&old_hotkey, &coldkey, stake_amount); + + // Perform the swap + assert_ok!(SubtensorModule::swap_stake( + &old_hotkey, + &new_hotkey, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_is_network_member_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let mut weight = Weight::zero(); + + // Initialize IsNetworkMember for old_hotkey + for netuid in &netuid_is_member { + IsNetworkMember::::insert(&old_hotkey, netuid, true); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_is_network_member( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the swap + for netuid in &netuid_is_member { + assert!(IsNetworkMember::::contains_key(&new_hotkey, netuid)); + assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); + } + }); +} + +#[test] +fn test_swap_is_network_member_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let mut weight = Weight::zero(); + + // Initialize IsNetworkMember for old_hotkey + for netuid in &netuid_is_member { + IsNetworkMember::::insert(&old_hotkey, netuid, true); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_is_network_member( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_axons_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let axon_info = AxonInfo { + block: 100, + version: 1, + ip: 0x1234567890abcdef, + port: 8080, + ip_type: 4, + protocol: 1, + placeholder1: 0, + placeholder2: 0, + }; + let mut weight = Weight::zero(); + + // Initialize Axons for old_hotkey + for netuid in &netuid_is_member { + Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_axons( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the swap + for netuid in &netuid_is_member { + assert_eq!(Axons::::get(netuid, &new_hotkey).unwrap(), axon_info); + assert!(!Axons::::contains_key(netuid, &old_hotkey)); + } + }); +} + +#[test] +fn test_swap_axons_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let axon_info = AxonInfo { + block: 100, + version: 1, + ip: 0x1234567890abcdef, + port: 8080, + ip_type: 4, + protocol: 1, + placeholder1: 0, + placeholder2: 0, + }; + let mut weight = Weight::zero(); + + // Initialize Axons for old_hotkey + for netuid in &netuid_is_member { + Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_axons( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_keys_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let uid = 42u16; + let mut weight = Weight::zero(); + + // Initialize Keys for old_hotkey + for netuid in &netuid_is_member { + log::info!("Inserting old_hotkey:{:?} netuid:{:?}", old_hotkey, netuid); + Keys::::insert(*netuid, uid, &old_hotkey); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_keys( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the swap + for netuid in &netuid_is_member { + log::info!("neutuid, uid, hotkey: {:?}, {:?}, {:?}", netuid, uid, new_hotkey); + assert_eq!(Keys::::get(netuid, uid), new_hotkey); + } + }); +} + +#[test] +fn test_swap_keys_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let uid = 42u16; + let mut weight = Weight::zero(); + + // Initialize Keys for old_hotkey + for netuid in &netuid_is_member { + Keys::::insert(*netuid, uid, old_hotkey.clone()); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_keys( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_loaded_emission_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let se = 100u64; + let ve = 200u64; + let mut weight = Weight::zero(); + + // Initialize LoadedEmission for old_hotkey + for netuid in &netuid_is_member { + LoadedEmission::::mutate(netuid, |emission_exists| { + if let Some(emissions) = emission_exists { + emissions.push((old_hotkey.clone(), se, ve)); + } else { + *emission_exists = Some(vec![(old_hotkey.clone(), se, ve)]); + } + }); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_loaded_emission( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the swap + for netuid in &netuid_is_member { + let emissions = LoadedEmission::::get(netuid).unwrap(); + assert!(emissions.iter().any(|(hk, _, _)| hk == &new_hotkey)); + assert!(!emissions.iter().any(|(hk, _, _)| hk == &old_hotkey)); + } + }); +} + +#[test] +fn test_swap_loaded_emission_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + // let uid = 42u64; + let se = 100u64; + let ve = 200u64; + let mut weight = Weight::zero(); + + // Initialize LoadedEmission for old_hotkey + for netuid in &netuid_is_member { + LoadedEmission::::mutate(netuid, |emission_exists| { + if let Some(emissions) = emission_exists { + emissions.push((old_hotkey.clone(), se, ve)); + } else { + *emission_exists = Some(vec![(old_hotkey.clone(), se, ve)]); + } + }); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_loaded_emission( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_uids_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let uid = 42u16; + let mut weight = Weight::zero(); + + // Initialize Uids for old_hotkey + for netuid in &netuid_is_member { + Uids::::insert(netuid, &old_hotkey, uid); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_uids( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the swap + for netuid in &netuid_is_member { + assert_eq!(Uids::::get(netuid, &new_hotkey).unwrap(), uid); + assert!(!Uids::::contains_key(netuid, &old_hotkey)); + } + }); +} + +#[test] +fn test_swap_uids_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let uid = 42u16; + let mut weight = Weight::zero(); + + // Initialize Uids for old_hotkey + for netuid in &netuid_is_member { + Uids::::insert(netuid, &old_hotkey, uid); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_uids( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_prometheus_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let prometheus_info = PrometheusInfo { + block: 100, + version: 1, + ip: 0x1234567890abcdef, + port: 8080, + ip_type: 4, + }; + let mut weight = Weight::zero(); + + // Initialize Prometheus for old_hotkey + for netuid in &netuid_is_member { + Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_prometheus( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the swap + for netuid in &netuid_is_member { + assert_eq!( + Prometheus::::get(netuid, &new_hotkey).unwrap(), + prometheus_info + ); + assert!(!Prometheus::::contains_key(netuid, &old_hotkey)); + } + }); +} + +#[test] +fn test_swap_prometheus_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let netuid_is_member = vec![1u16, 2u16]; + let prometheus_info = PrometheusInfo { + block: 100, + version: 1, + ip: 0x1234567890abcdef, + port: 8080, + ip_type: 4, + }; + let mut weight = Weight::zero(); + + // Initialize Prometheus for old_hotkey + for netuid in &netuid_is_member { + Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + } + + // Perform the swap + assert_ok!(SubtensorModule::swap_prometheus( + &old_hotkey, + &new_hotkey, + &netuid_is_member, + &mut weight + )); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake = (1000u64, 42u64); // Example tuple value + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey + TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, stake); + + // Perform the swap + assert_ok!( + SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( + &old_hotkey, + &new_hotkey, + &mut weight + ) + ); + + // Verify the swap + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), + stake + ); + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( + &old_hotkey, + &coldkey + )); + }); +} + +#[test] +fn test_swap_total_hotkey_coldkey_stakes_this_interval_weight_update() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake = (1000u64, 42u64); + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey + TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, stake); + + // Perform the swap + assert_ok!( + SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( + &old_hotkey, + &new_hotkey, + &mut weight + ) + ); + + // Verify the weight update + let expected_weight = ::DbWeight::get().writes(2); + assert_eq!(weight, expected_weight); + }); +} From 616c3228b9c2152c25572814268aaf9dc88c3e5c Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 15:29:54 +0400 Subject: [PATCH 024/185] chore: lints --- pallets/subtensor/src/swap.rs | 17 +++++++++----- pallets/subtensor/tests/swap.rs | 40 +++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 4d7aaf6f7..aa0bbf812 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -248,7 +248,7 @@ impl Pallet { pub fn swap_is_network_member( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, - netuid_is_member: &Vec, + netuid_is_member: &[u16], weight: &mut Weight, ) -> DispatchResult { let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); @@ -275,7 +275,7 @@ impl Pallet { pub fn swap_axons( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, - netuid_is_member: &Vec, + netuid_is_member: &[u16], weight: &mut Weight, ) -> DispatchResult { for netuid in netuid_is_member.iter() { @@ -368,7 +368,7 @@ impl Pallet { pub fn swap_uids( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, - netuid_is_member: &Vec, + netuid_is_member: &[u16], weight: &mut Weight, ) -> DispatchResult { for netuid in netuid_is_member.iter() { @@ -396,7 +396,7 @@ impl Pallet { pub fn swap_prometheus( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, - netuid_is_member: &Vec, + netuid_is_member: &[u16], weight: &mut Weight, ) -> DispatchResult { for netuid in netuid_is_member.iter() { @@ -425,10 +425,15 @@ impl Pallet { new_hotkey: &T::AccountId, weight: &mut Weight, ) -> DispatchResult { - let stakes: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); + let stakes: Vec<(T::AccountId, (u64, u64))> = + TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); log::info!("Stakes to swap: {:?}", stakes); for (coldkey, stake) in stakes { - log::info!("Swapping stake for coldkey: {:?}, stake: {:?}", coldkey, stake); + log::info!( + "Swapping stake for coldkey: {:?}, stake: {:?}", + coldkey, + stake + ); TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake); TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index d6052c8fc..980f5b5a5 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -145,13 +145,16 @@ fn test_do_swap_hotkey_ok_robust() { coldkeys.push(U256::from(i * 2 + 11)); } - // Setup initial state for netuid in 1..=num_subnets { add_network(netuid, tempo, 0); SubtensorModule::set_max_registrations_per_block(netuid, 20); SubtensorModule::set_target_registrations_per_interval(netuid, 1000); - log::info!("Registrations this interval for netuid {:?} is {:?}", netuid, SubtensorModule::get_target_registrations_per_interval(netuid)); + log::info!( + "Registrations this interval for netuid {:?} is {:?}", + netuid, + SubtensorModule::get_target_registrations_per_interval(netuid) + ); for i in 0..10 { register_ok_neuron(netuid, old_hotkeys[i], coldkeys[i], 0); } @@ -211,7 +214,9 @@ fn test_do_swap_hotkey_ok_robust() { let mut weight = Weight::zero(); // UIDs - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + for netuid in + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + { assert_eq!( Uids::::get(netuid, &new_hotkeys[i]), Uids::::get(netuid, &old_hotkeys[i]) @@ -219,7 +224,9 @@ fn test_do_swap_hotkey_ok_robust() { } // Prometheus - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + for netuid in + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + { assert_eq!( Prometheus::::get(netuid, &new_hotkeys[i]), Prometheus::::get(netuid, &old_hotkeys[i]) @@ -227,7 +234,9 @@ fn test_do_swap_hotkey_ok_robust() { } // LoadedEmission - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { + for netuid in + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + { assert_eq!( LoadedEmission::::get(netuid).unwrap(), LoadedEmission::::get(netuid).unwrap() @@ -235,9 +244,17 @@ fn test_do_swap_hotkey_ok_robust() { } // IsNetworkMember - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { - assert!(IsNetworkMember::::contains_key(&new_hotkeys[i], netuid)); - assert!(!IsNetworkMember::::contains_key(&old_hotkeys[i], netuid)); + for netuid in + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + { + assert!(IsNetworkMember::::contains_key( + &new_hotkeys[i], + netuid + )); + assert!(!IsNetworkMember::::contains_key( + &old_hotkeys[i], + netuid + )); } // Owner @@ -833,7 +850,12 @@ fn test_swap_keys_success() { // Verify the swap for netuid in &netuid_is_member { - log::info!("neutuid, uid, hotkey: {:?}, {:?}, {:?}", netuid, uid, new_hotkey); + log::info!( + "neutuid, uid, hotkey: {:?}, {:?}, {:?}", + netuid, + uid, + new_hotkey + ); assert_eq!(Keys::::get(netuid, uid), new_hotkey); } }); From affd4b8b5a6c575f7216c21fda5e162316894ad7 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 16:02:11 +0400 Subject: [PATCH 025/185] chore: clippy --- pallets/subtensor/tests/swap.rs | 189 ++++++++++++++++---------------- 1 file changed, 94 insertions(+), 95 deletions(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 980f5b5a5..c9cb2a7af 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -55,24 +55,24 @@ fn test_do_swap_hotkey_ok() { // Verify raw storage maps // Stake - for (coldkey, stake_amount) in Stake::::iter_prefix(&old_hotkey) { - assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); + for (coldkey, stake_amount) in Stake::::iter_prefix(old_hotkey) { + assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); } let mut weight = Weight::zero(); // UIDs for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { assert_eq!( - Uids::::get(netuid, &new_hotkey), - Uids::::get(netuid, &old_hotkey) + Uids::::get(netuid, new_hotkey), + Uids::::get(netuid, old_hotkey) ); } // Prometheus for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { assert_eq!( - Prometheus::::get(netuid, &new_hotkey), - Prometheus::::get(netuid, &old_hotkey) + Prometheus::::get(netuid, new_hotkey), + Prometheus::::get(netuid, old_hotkey) ); } @@ -86,43 +86,43 @@ fn test_do_swap_hotkey_ok() { // IsNetworkMember for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { - assert!(IsNetworkMember::::contains_key(&new_hotkey, netuid)); - assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); + assert!(IsNetworkMember::::contains_key(new_hotkey, netuid)); + assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); } // Owner - assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert_eq!(Owner::::get(new_hotkey), coldkey); // TotalHotkeyStake assert_eq!( - TotalHotkeyStake::::get(&new_hotkey), - TotalHotkeyStake::::get(&old_hotkey) + TotalHotkeyStake::::get(new_hotkey), + TotalHotkeyStake::::get(old_hotkey) ); // Delegates assert_eq!( - Delegates::::get(&new_hotkey), - Delegates::::get(&old_hotkey) + Delegates::::get(new_hotkey), + Delegates::::get(old_hotkey) ); // LastTxBlock assert_eq!( - LastTxBlock::::get(&new_hotkey), - LastTxBlock::::get(&old_hotkey) + LastTxBlock::::get(new_hotkey), + LastTxBlock::::get(old_hotkey) ); // Axons for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { assert_eq!( - Axons::::get(netuid, &new_hotkey), - Axons::::get(netuid, &old_hotkey) + Axons::::get(netuid, new_hotkey), + Axons::::get(netuid, old_hotkey) ); } // TotalHotkeyColdkeyStakesThisInterval assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), - TotalHotkeyColdkeyStakesThisInterval::::get(&old_hotkey, &coldkey) + TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), + TotalHotkeyColdkeyStakesThisInterval::::get(old_hotkey, coldkey) ); }); } @@ -161,8 +161,8 @@ fn test_do_swap_hotkey_ok_robust() { } // Add balance to coldkeys for swap cost - for i in 0..10 { - SubtensorModule::add_balance_to_coldkey_account(&coldkeys[i], swap_cost); + for coldkey in coldkeys.iter().take(10) { + SubtensorModule::add_balance_to_coldkey_account(coldkey, swap_cost); } // Perform the swaps for only two hotkeys @@ -178,7 +178,7 @@ fn test_do_swap_hotkey_ok_robust() { )); // Verify the swaps - for netuid in 1..=num_subnets { + for _netuid in 1..=num_subnets { for i in 0..10 { if i == 0 || i == 1 { assert_eq!( @@ -208,8 +208,8 @@ fn test_do_swap_hotkey_ok_robust() { // Verify raw storage maps // Stake - for (coldkey, stake_amount) in Stake::::iter_prefix(&old_hotkeys[i]) { - assert_eq!(Stake::::get(&new_hotkeys[i], &coldkey), stake_amount); + for (coldkey, stake_amount) in Stake::::iter_prefix(old_hotkeys[i]) { + assert_eq!(Stake::::get(new_hotkeys[i], coldkey), stake_amount); } let mut weight = Weight::zero(); @@ -218,8 +218,8 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { assert_eq!( - Uids::::get(netuid, &new_hotkeys[i]), - Uids::::get(netuid, &old_hotkeys[i]) + Uids::::get(netuid, new_hotkeys[i]), + Uids::::get(netuid, old_hotkeys[i]) ); } @@ -228,8 +228,8 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { assert_eq!( - Prometheus::::get(netuid, &new_hotkeys[i]), - Prometheus::::get(netuid, &old_hotkeys[i]) + Prometheus::::get(netuid, new_hotkeys[i]), + Prometheus::::get(netuid, old_hotkeys[i]) ); } @@ -248,17 +248,17 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() { assert!(IsNetworkMember::::contains_key( - &new_hotkeys[i], + new_hotkeys[i], netuid )); assert!(!IsNetworkMember::::contains_key( - &old_hotkeys[i], + old_hotkeys[i], netuid )); } // Owner - assert_eq!(Owner::::get(&new_hotkeys[i]), coldkeys[i]); + assert_eq!(Owner::::get(new_hotkeys[i]), coldkeys[i]); } else { // Ensure other hotkeys remain unchanged assert_eq!( @@ -312,7 +312,7 @@ fn test_swap_owner_success() { let mut weight = Weight::zero(); // Initialize Owner for old_hotkey - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); // Perform the swap assert_ok!(SubtensorModule::swap_owner( @@ -323,8 +323,8 @@ fn test_swap_owner_success() { )); // Verify the swap - assert_eq!(Owner::::get(&new_hotkey), coldkey); - assert!(!Owner::::contains_key(&old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); }); } @@ -337,7 +337,7 @@ fn test_swap_owner_old_hotkey_not_exist() { let mut weight = Weight::zero(); // Ensure old_hotkey does not exist - assert!(!Owner::::contains_key(&old_hotkey)); + assert!(!Owner::::contains_key(old_hotkey)); // Perform the swap assert_ok!(SubtensorModule::swap_owner( @@ -348,8 +348,8 @@ fn test_swap_owner_old_hotkey_not_exist() { )); // Verify the swap - assert_eq!(Owner::::get(&new_hotkey), coldkey); - assert!(!Owner::::contains_key(&old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); }); } @@ -363,8 +363,8 @@ fn test_swap_owner_new_hotkey_already_exists() { let mut weight = Weight::zero(); // Initialize Owner for old_hotkey and new_hotkey - Owner::::insert(&old_hotkey, &coldkey); - Owner::::insert(&new_hotkey, &another_coldkey); + Owner::::insert(old_hotkey, coldkey); + Owner::::insert(new_hotkey, another_coldkey); // Perform the swap assert_ok!(SubtensorModule::swap_owner( @@ -375,8 +375,8 @@ fn test_swap_owner_new_hotkey_already_exists() { )); // Verify the swap - assert_eq!(Owner::::get(&new_hotkey), coldkey); - assert!(!Owner::::contains_key(&old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); }); } @@ -389,7 +389,7 @@ fn test_swap_owner_weight_update() { let mut weight = Weight::zero(); // Initialize Owner for old_hotkey - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); // Perform the swap assert_ok!(SubtensorModule::swap_owner( @@ -414,7 +414,7 @@ fn test_swap_total_hotkey_stake_success() { let mut weight = Weight::zero(); // Initialize TotalHotkeyStake for old_hotkey - TotalHotkeyStake::::insert(&old_hotkey, total_stake); + TotalHotkeyStake::::insert(old_hotkey, total_stake); // Perform the swap assert_ok!(SubtensorModule::swap_total_hotkey_stake( @@ -424,8 +424,8 @@ fn test_swap_total_hotkey_stake_success() { )); // Verify the swap - assert_eq!(TotalHotkeyStake::::get(&new_hotkey), total_stake); - assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + assert_eq!(TotalHotkeyStake::::get(new_hotkey), total_stake); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); }); } @@ -437,7 +437,7 @@ fn test_swap_total_hotkey_stake_old_hotkey_not_exist() { let mut weight = Weight::zero(); // Ensure old_hotkey does not exist - assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); // Perform the swap assert_ok!(SubtensorModule::swap_total_hotkey_stake( @@ -447,7 +447,7 @@ fn test_swap_total_hotkey_stake_old_hotkey_not_exist() { )); // Verify that new_hotkey does not have a stake - assert!(!TotalHotkeyStake::::contains_key(&new_hotkey)); + assert!(!TotalHotkeyStake::::contains_key(new_hotkey)); }); } @@ -460,7 +460,7 @@ fn test_swap_total_hotkey_stake_weight_update() { let mut weight = Weight::zero(); // Initialize TotalHotkeyStake for old_hotkey - TotalHotkeyStake::::insert(&old_hotkey, total_stake); + TotalHotkeyStake::::insert(old_hotkey, total_stake); // Perform the swap assert_ok!(SubtensorModule::swap_total_hotkey_stake( @@ -484,7 +484,7 @@ fn test_swap_delegates_success() { let mut weight = Weight::zero(); // Initialize Delegates for old_hotkey - Delegates::::insert(&old_hotkey, delegate_take); + Delegates::::insert(old_hotkey, delegate_take); // Perform the swap assert_ok!(SubtensorModule::swap_delegates( @@ -494,8 +494,8 @@ fn test_swap_delegates_success() { )); // Verify the swap - assert_eq!(Delegates::::get(&new_hotkey), delegate_take); - assert!(!Delegates::::contains_key(&old_hotkey)); + assert_eq!(Delegates::::get(new_hotkey), delegate_take); + assert!(!Delegates::::contains_key(old_hotkey)); }); } @@ -507,7 +507,7 @@ fn test_swap_delegates_old_hotkey_not_exist() { let mut weight = Weight::zero(); // Ensure old_hotkey does not exist - assert!(!Delegates::::contains_key(&old_hotkey)); + assert!(!Delegates::::contains_key(old_hotkey)); // Perform the swap assert_ok!(SubtensorModule::swap_delegates( @@ -517,7 +517,7 @@ fn test_swap_delegates_old_hotkey_not_exist() { )); // Verify that new_hotkey does not have a delegate - assert!(!Delegates::::contains_key(&new_hotkey)); + assert!(!Delegates::::contains_key(new_hotkey)); }); } @@ -530,7 +530,7 @@ fn test_swap_delegates_weight_update() { let mut weight = Weight::zero(); // Initialize Delegates for old_hotkey - Delegates::::insert(&old_hotkey, delegate_take); + Delegates::::insert(old_hotkey, delegate_take); // Perform the swap assert_ok!(SubtensorModule::swap_delegates( @@ -554,7 +554,7 @@ fn test_swap_last_tx_block_success() { let mut weight = Weight::zero(); // Initialize LastTxBlock for old_hotkey - LastTxBlock::::insert(&old_hotkey, last_tx_block); + LastTxBlock::::insert(old_hotkey, last_tx_block); // Perform the swap assert_ok!(SubtensorModule::swap_last_tx_block( @@ -564,8 +564,8 @@ fn test_swap_last_tx_block_success() { )); // Verify the swap - assert_eq!(LastTxBlock::::get(&new_hotkey), last_tx_block); - assert!(!LastTxBlock::::contains_key(&old_hotkey)); + assert_eq!(LastTxBlock::::get(new_hotkey), last_tx_block); + assert!(!LastTxBlock::::contains_key(old_hotkey)); }); } @@ -577,7 +577,7 @@ fn test_swap_last_tx_block_old_hotkey_not_exist() { let mut weight = Weight::zero(); // Ensure old_hotkey does not exist - assert!(!LastTxBlock::::contains_key(&old_hotkey)); + assert!(!LastTxBlock::::contains_key(old_hotkey)); // Perform the swap assert_ok!(SubtensorModule::swap_last_tx_block( @@ -587,7 +587,7 @@ fn test_swap_last_tx_block_old_hotkey_not_exist() { )); // Verify that new_hotkey does not have a last transaction block - assert!(!LastTxBlock::::contains_key(&new_hotkey)); + assert!(!LastTxBlock::::contains_key(new_hotkey)); }); } @@ -600,7 +600,7 @@ fn test_swap_last_tx_block_weight_update() { let mut weight = Weight::zero(); // Initialize LastTxBlock for old_hotkey - LastTxBlock::::insert(&old_hotkey, last_tx_block); + LastTxBlock::::insert(old_hotkey, last_tx_block); // Perform the swap assert_ok!(SubtensorModule::swap_last_tx_block( @@ -625,7 +625,7 @@ fn test_swap_stake_success() { let mut weight = Weight::zero(); // Initialize Stake for old_hotkey - Stake::::insert(&old_hotkey, &coldkey, stake_amount); + Stake::::insert(old_hotkey, coldkey, stake_amount); // Perform the swap assert_ok!(SubtensorModule::swap_stake( @@ -635,8 +635,8 @@ fn test_swap_stake_success() { )); // Verify the swap - assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); + assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); }); } @@ -650,10 +650,10 @@ fn test_swap_stake_old_hotkey_not_exist() { let mut weight = Weight::zero(); // Initialize Stake for old_hotkey - Stake::::insert(&old_hotkey, &coldkey, stake_amount); + Stake::::insert(old_hotkey, coldkey, stake_amount); // Ensure old_hotkey has a stake - assert!(Stake::::contains_key(&old_hotkey, &coldkey)); + assert!(Stake::::contains_key(old_hotkey, coldkey)); // Perform the swap assert_ok!(SubtensorModule::swap_stake( @@ -663,8 +663,8 @@ fn test_swap_stake_old_hotkey_not_exist() { )); // Verify that new_hotkey has the stake and old_hotkey does not - assert!(Stake::::contains_key(&new_hotkey, &coldkey)); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); + assert!(Stake::::contains_key(new_hotkey, coldkey)); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); }); } @@ -678,7 +678,7 @@ fn test_swap_stake_weight_update() { let mut weight = Weight::zero(); // Initialize Stake for old_hotkey - Stake::::insert(&old_hotkey, &coldkey, stake_amount); + Stake::::insert(old_hotkey, coldkey, stake_amount); // Perform the swap assert_ok!(SubtensorModule::swap_stake( @@ -703,7 +703,7 @@ fn test_swap_is_network_member_success() { // Initialize IsNetworkMember for old_hotkey for netuid in &netuid_is_member { - IsNetworkMember::::insert(&old_hotkey, netuid, true); + IsNetworkMember::::insert(old_hotkey, netuid, true); } // Perform the swap @@ -716,8 +716,8 @@ fn test_swap_is_network_member_success() { // Verify the swap for netuid in &netuid_is_member { - assert!(IsNetworkMember::::contains_key(&new_hotkey, netuid)); - assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); + assert!(IsNetworkMember::::contains_key(new_hotkey, netuid)); + assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); } }); } @@ -732,7 +732,7 @@ fn test_swap_is_network_member_weight_update() { // Initialize IsNetworkMember for old_hotkey for netuid in &netuid_is_member { - IsNetworkMember::::insert(&old_hotkey, netuid, true); + IsNetworkMember::::insert(old_hotkey, netuid, true); } // Perform the swap @@ -769,7 +769,7 @@ fn test_swap_axons_success() { // Initialize Axons for old_hotkey for netuid in &netuid_is_member { - Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + Axons::::insert(netuid, old_hotkey, axon_info.clone()); } // Perform the swap @@ -782,8 +782,8 @@ fn test_swap_axons_success() { // Verify the swap for netuid in &netuid_is_member { - assert_eq!(Axons::::get(netuid, &new_hotkey).unwrap(), axon_info); - assert!(!Axons::::contains_key(netuid, &old_hotkey)); + assert_eq!(Axons::::get(netuid, new_hotkey).unwrap(), axon_info); + assert!(!Axons::::contains_key(netuid, old_hotkey)); } }); } @@ -808,7 +808,7 @@ fn test_swap_axons_weight_update() { // Initialize Axons for old_hotkey for netuid in &netuid_is_member { - Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + Axons::::insert(netuid, old_hotkey, axon_info.clone()); } // Perform the swap @@ -837,7 +837,7 @@ fn test_swap_keys_success() { // Initialize Keys for old_hotkey for netuid in &netuid_is_member { log::info!("Inserting old_hotkey:{:?} netuid:{:?}", old_hotkey, netuid); - Keys::::insert(*netuid, uid, &old_hotkey); + Keys::::insert(*netuid, uid, old_hotkey); } // Perform the swap @@ -872,7 +872,7 @@ fn test_swap_keys_weight_update() { // Initialize Keys for old_hotkey for netuid in &netuid_is_member { - Keys::::insert(*netuid, uid, old_hotkey.clone()); + Keys::::insert(*netuid, uid, old_hotkey); } // Perform the swap @@ -903,9 +903,9 @@ fn test_swap_loaded_emission_success() { for netuid in &netuid_is_member { LoadedEmission::::mutate(netuid, |emission_exists| { if let Some(emissions) = emission_exists { - emissions.push((old_hotkey.clone(), se, ve)); + emissions.push((old_hotkey, se, ve)); } else { - *emission_exists = Some(vec![(old_hotkey.clone(), se, ve)]); + *emission_exists = Some(vec![(old_hotkey, se, ve)]); } }); } @@ -942,9 +942,9 @@ fn test_swap_loaded_emission_weight_update() { for netuid in &netuid_is_member { LoadedEmission::::mutate(netuid, |emission_exists| { if let Some(emissions) = emission_exists { - emissions.push((old_hotkey.clone(), se, ve)); + emissions.push((old_hotkey, se, ve)); } else { - *emission_exists = Some(vec![(old_hotkey.clone(), se, ve)]); + *emission_exists = Some(vec![(old_hotkey, se, ve)]); } }); } @@ -974,7 +974,7 @@ fn test_swap_uids_success() { // Initialize Uids for old_hotkey for netuid in &netuid_is_member { - Uids::::insert(netuid, &old_hotkey, uid); + Uids::::insert(netuid, old_hotkey, uid); } // Perform the swap @@ -987,8 +987,8 @@ fn test_swap_uids_success() { // Verify the swap for netuid in &netuid_is_member { - assert_eq!(Uids::::get(netuid, &new_hotkey).unwrap(), uid); - assert!(!Uids::::contains_key(netuid, &old_hotkey)); + assert_eq!(Uids::::get(netuid, new_hotkey).unwrap(), uid); + assert!(!Uids::::contains_key(netuid, old_hotkey)); } }); } @@ -1004,7 +1004,7 @@ fn test_swap_uids_weight_update() { // Initialize Uids for old_hotkey for netuid in &netuid_is_member { - Uids::::insert(netuid, &old_hotkey, uid); + Uids::::insert(netuid, old_hotkey, uid); } // Perform the swap @@ -1038,7 +1038,7 @@ fn test_swap_prometheus_success() { // Initialize Prometheus for old_hotkey for netuid in &netuid_is_member { - Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); } // Perform the swap @@ -1052,10 +1052,10 @@ fn test_swap_prometheus_success() { // Verify the swap for netuid in &netuid_is_member { assert_eq!( - Prometheus::::get(netuid, &new_hotkey).unwrap(), + Prometheus::::get(netuid, new_hotkey).unwrap(), prometheus_info ); - assert!(!Prometheus::::contains_key(netuid, &old_hotkey)); + assert!(!Prometheus::::contains_key(netuid, old_hotkey)); } }); } @@ -1077,7 +1077,7 @@ fn test_swap_prometheus_weight_update() { // Initialize Prometheus for old_hotkey for netuid in &netuid_is_member { - Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); } // Perform the swap @@ -1104,7 +1104,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { let mut weight = Weight::zero(); // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey - TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, stake); + TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // Perform the swap assert_ok!( @@ -1117,12 +1117,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { // Verify the swap assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), + TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), stake ); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - &old_hotkey, - &coldkey + old_hotkey, coldkey )); }); } @@ -1137,7 +1136,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_weight_update() { let mut weight = Weight::zero(); // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey - TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, stake); + TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // Perform the swap assert_ok!( From 04637f810c44ffb272e956fa19ed1fbc0c459686 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 17:31:13 +0400 Subject: [PATCH 026/185] chore: give 1k on faucet for easier to create networks locally --- pallets/subtensor/src/registration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index da1d84183..4688bcbb5 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -394,7 +394,7 @@ impl Pallet { UsedWork::::insert(work.clone(), current_block_number); // --- 5. Add Balance via faucet. - let balance_to_add: u64 = 100_000_000_000; + let balance_to_add: u64 = 1_000_000_000_000; Self::coinbase(100_000_000_000); // We are creating tokens here from the coinbase. Self::add_balance_to_coldkey_account(&coldkey, balance_to_add); From 62e5907ccaf9509888faecdbd7620146a4a2fa85 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 17 Jun 2024 23:25:14 +0900 Subject: [PATCH 027/185] Correct the expected weights on the register extrinsic --- pallets/subtensor/tests/registration.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 0da10bc48..8af83ad74 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -38,7 +38,9 @@ fn test_registration_subscribe_ok_dispatch_info_ok() { assert_eq!( call.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_parts(192_000_000, 0), + weight: frame_support::weights::Weight::from_parts(192_000_000, 0) + .saturating_add(::DbWeight::get().reads(24)) + .saturating_add(::DbWeight::get().writes(22)), class: DispatchClass::Normal, pays_fee: Pays::No } From 9667b49fdadfd8d3d17216dc7635582103054d93 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:26:39 +0400 Subject: [PATCH 028/185] Update pallets/subtensor/src/swap.rs Co-authored-by: Keith --- pallets/subtensor/src/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index aa0bbf812..8185ba0f9 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -7,7 +7,7 @@ impl Pallet { /// /// # Arguments /// - /// * `origin` - The origin of the transaction. + /// * `origin` - The origin of the transaction, and also the coldkey account. /// * `old_hotkey` - The old hotkey to be swapped. /// * `new_hotkey` - The new hotkey to replace the old one. /// From b60b544839f1f9c5811f05404116682991cc2e26 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 21:31:34 +0400 Subject: [PATCH 029/185] chore: updates from using real weights in tests --- pallets/subtensor/tests/serving.rs | 4 ++-- pallets/subtensor/tests/staking.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 851edeee2..41e9888cc 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -50,7 +50,7 @@ fn test_serving_subscribe_ok_dispatch_info_ok() { assert_eq!( call.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_parts(46_000_000, 0), + weight: frame_support::weights::Weight::from_parts(246_000_000, 0), class: DispatchClass::Normal, pays_fee: Pays::No } @@ -295,7 +295,7 @@ fn test_prometheus_serving_subscribe_ok_dispatch_info_ok() { assert_eq!( call.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_parts(45_000_000, 0), + weight: frame_support::weights::Weight::from_parts(245_000_000, 0), class: DispatchClass::Normal, pays_fee: Pays::No } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 766b3a495..529332f04 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -26,7 +26,7 @@ fn test_add_stake_dispatch_info_ok() { assert_eq!( call.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_parts(124_000_000, 0), + weight: frame_support::weights::Weight::from_parts(1_074_000_000, 0), class: DispatchClass::Normal, pays_fee: Pays::No } @@ -532,7 +532,7 @@ fn test_remove_stake_dispatch_info_ok() { assert_eq!( call.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_parts(111_000_000, 0) + weight: frame_support::weights::Weight::from_parts(1_061_000_000, 0) .add_proof_size(43991), class: DispatchClass::Normal, pays_fee: Pays::No From 31f320ccf1fabf29869c095568422730b7f57cb1 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 22:21:54 +0400 Subject: [PATCH 030/185] chore: review comments , make swap cost a constant --- pallets/admin-utils/tests/mock.rs | 2 ++ pallets/subtensor/src/lib.rs | 8 ++++++++ pallets/subtensor/src/swap.rs | 22 ++++++++++++---------- pallets/subtensor/src/utils.rs | 5 +++++ pallets/subtensor/tests/mock.rs | 2 ++ pallets/subtensor/tests/registration.rs | 4 +++- runtime/src/lib.rs | 1 + 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 16a1f79f4..a78eb6d3d 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -110,6 +110,7 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 1; + pub const InitialHotkeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -164,6 +165,7 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; + type HotkeySwapCost = InitialHotkeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 049a52082..7ca2a2777 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -239,6 +239,9 @@ pub mod pallet { /// Initial target stakes per interval issuance. #[pallet::constant] type InitialTargetStakesPerInterval: Get; + /// Cost of swapping a hotkey. + #[pallet::constant] + type HotkeySwapCost: Get; /// The upper bound for the alpha parameter. Used for Liquid Alpha. #[pallet::constant] type AlphaHigh: Get; @@ -272,6 +275,11 @@ pub mod pallet { pub fn TotalSupply() -> u64 { 21_000_000_000_000_000 // Rao => 21_000_000 Tao } + /// Hotkey swap cost. + #[pallet::type_value] + pub fn HotkeySwapCost() -> u64 { + 1_000_000_000 + } /// Default total stake. #[pallet::type_value] pub fn DefaultDefaultTake() -> u16 { diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 8185ba0f9..873dcaa12 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -29,7 +29,15 @@ impl Pallet { ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; - let mut weight = T::DbWeight::get().reads_writes(2, 0); + let mut weight = T::DbWeight::get().reads(2); + + ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); ensure!( Self::coldkey_owns_hotkey(&coldkey, old_hotkey), Error::::NonAssociatedColdKey @@ -41,18 +49,12 @@ impl Pallet { Error::::HotKeySetTxRateLimitExceeded ); - weight.saturating_accrue(T::DbWeight::get().reads(2)); - - ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - weight .saturating_accrue(T::DbWeight::get().reads((TotalNetworks::::get() + 1u16) as u64)); - let swap_cost = 1_000_000_000u64; + let swap_cost = Self::get_hotkey_swap_cost(); + log::info!("Swap cost: {:?}", swap_cost); + ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapHotKey diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 918343564..faaaecebb 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -3,6 +3,7 @@ use crate::{ system::{ensure_root, ensure_signed_or_root}, Error, }; +use sp_core::Get; use sp_core::U256; use substrate_fixed::types::I32F32; @@ -663,6 +664,10 @@ impl Pallet { NominatorMinRequiredStake::::put(min_stake); } + pub fn get_hotkey_swap_cost() -> u64 { + T::HotkeySwapCost::get() + } + pub fn get_alpha_values(netuid: u16) -> (u16, u16) { AlphaValues::::get(netuid) } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 195063899..cf58f49d2 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -162,6 +162,7 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 2; + pub const InitialHotkeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -365,6 +366,7 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; + type HotkeySwapCost = InitialHotkeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 8af83ad74..77ec068fd 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -40,7 +40,9 @@ fn test_registration_subscribe_ok_dispatch_info_ok() { DispatchInfo { weight: frame_support::weights::Weight::from_parts(192_000_000, 0) .saturating_add(::DbWeight::get().reads(24)) - .saturating_add(::DbWeight::get().writes(22)), + .saturating_add( + ::DbWeight::get().writes(22) + ), class: DispatchClass::Normal, pays_fee: Pays::No } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1d01c0b92..4b8141928 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -879,6 +879,7 @@ impl pallet_subtensor::Config for Runtime { type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval; + type HotkeySwapCost = ConstU64<1_000_000_000>; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; From 8d7e1865f1947b80deac1d603d8132bfb1e1442e Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 17 Jun 2024 22:37:26 +0400 Subject: [PATCH 031/185] chore: runtime consts --- runtime/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4b8141928..98d267c6d 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -825,6 +825,7 @@ parameter_types! { pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; pub const SubtensorInitialTargetStakesPerInterval: u16 = 1; + pub const SubtensorInitialHotkeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -879,7 +880,7 @@ impl pallet_subtensor::Config for Runtime { type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval; - type HotkeySwapCost = ConstU64<1_000_000_000>; + type HotkeySwapCost = SubtensorInitialHotkeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; From 9eeff75a75d4e45382914bb6ca7d5c212ed0938d Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 01:09:59 +0400 Subject: [PATCH 032/185] chore: clear prefix for removing old value from stake map --- pallets/subtensor/src/swap.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 873dcaa12..5313113b7 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -227,11 +227,13 @@ impl Pallet { ) -> DispatchResult { let mut writes = 0; let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); + let stake_count = stakes.len() as u32; for (coldkey, stake_amount) in stakes { Stake::::insert(new_hotkey, &coldkey, stake_amount); - Stake::::remove(old_hotkey, &coldkey); + let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); writes += 2; // One write for insert and one for remove } + *weight += T::DbWeight::get().writes(writes as u64); Ok(()) } From 12161b09e5c1c93f2d9141a5cb244211a034e126 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 01:26:04 +0400 Subject: [PATCH 033/185] chore: pr comments assert keys --- pallets/subtensor/tests/swap.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index c9cb2a7af..04819211a 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -178,7 +178,7 @@ fn test_do_swap_hotkey_ok_robust() { )); // Verify the swaps - for _netuid in 1..=num_subnets { + for netuid in 1..=num_subnets { for i in 0..10 { if i == 0 || i == 1 { assert_eq!( @@ -259,6 +259,13 @@ fn test_do_swap_hotkey_ok_robust() { // Owner assert_eq!(Owner::::get(new_hotkeys[i]), coldkeys[i]); + + // Keys + for (uid, hotkey) in Keys::::iter_prefix(netuid) { + if hotkey == old_hotkeys[i] { + assert_eq!(Keys::::get(netuid, uid), new_hotkeys[i]); + } + } } else { // Ensure other hotkeys remain unchanged assert_eq!( From 966b5a2b3993ead1975b5c317e8df6f934d2dd30 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 01:28:57 +0400 Subject: [PATCH 034/185] chore: pr comments: remove last tx block --- pallets/subtensor/src/swap.rs | 24 ----------- pallets/subtensor/tests/swap.rs | 70 --------------------------------- 2 files changed, 94 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 5313113b7..4f9eaf047 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -65,7 +65,6 @@ impl Pallet { Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight)?; Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight)?; Self::swap_delegates(old_hotkey, new_hotkey, &mut weight)?; - Self::swap_last_tx_block(old_hotkey, new_hotkey, &mut weight)?; Self::swap_stake(old_hotkey, new_hotkey, &mut weight)?; // Store the value of is_network_member for the old key @@ -185,29 +184,6 @@ impl Pallet { Ok(()) } - /// Swaps the last transaction block of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. - pub fn swap_last_tx_block( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) -> DispatchResult { - if let Ok(last_tx) = LastTxBlock::::try_get(old_hotkey) { - LastTxBlock::::remove(old_hotkey); - LastTxBlock::::insert(new_hotkey, last_tx); - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - Ok(()) - } /// Swaps the stake of the hotkey. /// diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 04819211a..f73b6e3f2 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -552,76 +552,6 @@ fn test_swap_delegates_weight_update() { }); } -#[test] -fn test_swap_last_tx_block_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let last_tx_block = 100u64; - let mut weight = Weight::zero(); - - // Initialize LastTxBlock for old_hotkey - LastTxBlock::::insert(old_hotkey, last_tx_block); - - // Perform the swap - assert_ok!(SubtensorModule::swap_last_tx_block( - &old_hotkey, - &new_hotkey, - &mut weight - )); - - // Verify the swap - assert_eq!(LastTxBlock::::get(new_hotkey), last_tx_block); - assert!(!LastTxBlock::::contains_key(old_hotkey)); - }); -} - -#[test] -fn test_swap_last_tx_block_old_hotkey_not_exist() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let mut weight = Weight::zero(); - - // Ensure old_hotkey does not exist - assert!(!LastTxBlock::::contains_key(old_hotkey)); - - // Perform the swap - assert_ok!(SubtensorModule::swap_last_tx_block( - &old_hotkey, - &new_hotkey, - &mut weight - )); - - // Verify that new_hotkey does not have a last transaction block - assert!(!LastTxBlock::::contains_key(new_hotkey)); - }); -} - -#[test] -fn test_swap_last_tx_block_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let last_tx_block = 100u64; - let mut weight = Weight::zero(); - - // Initialize LastTxBlock for old_hotkey - LastTxBlock::::insert(old_hotkey, last_tx_block); - - // Perform the swap - assert_ok!(SubtensorModule::swap_last_tx_block( - &old_hotkey, - &new_hotkey, - &mut weight - )); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); - assert_eq!(weight, expected_weight); - }); -} - #[test] fn test_swap_stake_success() { new_test_ext(1).execute_with(|| { From 062fa09a1acf38763193a760796e38159c1fb61f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 01:36:55 +0400 Subject: [PATCH 035/185] chore: fmt --- pallets/subtensor/src/swap.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 4f9eaf047..e6f8fb6e6 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -184,7 +184,6 @@ impl Pallet { Ok(()) } - /// Swaps the stake of the hotkey. /// /// # Arguments From 52831fa9f687e285d2a5fba0666216128cf6b61a Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 18 Jun 2024 11:52:55 +0900 Subject: [PATCH 036/185] Use concrete numbers for weight expectations --- pallets/subtensor/tests/registration.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 77ec068fd..676d49a44 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -38,11 +38,7 @@ fn test_registration_subscribe_ok_dispatch_info_ok() { assert_eq!( call.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_parts(192_000_000, 0) - .saturating_add(::DbWeight::get().reads(24)) - .saturating_add( - ::DbWeight::get().writes(22) - ), + weight: frame_support::weights::Weight::from_parts(2_992_000_000, 0), class: DispatchClass::Normal, pays_fee: Pays::No } From b0e5673ccf5fba642fe78d7d0d7d2761157aaca3 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:28:45 +0400 Subject: [PATCH 037/185] Update pallets/subtensor/src/swap.rs Co-authored-by: orriin <167025436+orriin@users.noreply.github.com> --- pallets/subtensor/src/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index e6f8fb6e6..0b527b935 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -53,7 +53,7 @@ impl Pallet { .saturating_accrue(T::DbWeight::get().reads((TotalNetworks::::get() + 1u16) as u64)); let swap_cost = Self::get_hotkey_swap_cost(); - log::info!("Swap cost: {:?}", swap_cost); + log::debug!("Swap cost: {:?}", swap_cost); ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), From 5938d1609f4e81db8ce7f0b22b983cb2e4f6f432 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 11:54:37 +0400 Subject: [PATCH 038/185] chore: add test_swap_hotkey_tx_rate_limit_exceeded --- pallets/subtensor/tests/swap.rs | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index f73b6e3f2..3a2118c4a 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -282,6 +282,63 @@ fn test_do_swap_hotkey_ok_robust() { }); } +#[test] +fn test_swap_hotkey_tx_rate_limit_exceeded() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey_1 = U256::from(2); + let new_hotkey_2 = U256::from(4); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64*2; + + let tx_rate_limit = 1; + + // Get the current transaction rate limit + let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); + log::info!("current_tx_rate_limit: {:?}", current_tx_rate_limit); + + // Set the transaction rate limit + SubtensorModule::set_tx_rate_limit(tx_rate_limit); + // assert the rate limit is set to 1000 blocks + assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); + + // Perform the first swap + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey_1 + )); + + + // Attempt to perform another swap immediately, which should fail due to rate limit + assert_err!( + SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey_1, + &new_hotkey_2 + ), + Error::::HotKeySetTxRateLimitExceeded + ); + + + // move in time past the rate limit + step_block(1001); + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey_1, + &new_hotkey_2 + )); + + }); +} + #[test] fn test_do_swap_hotkey_err_not_owner() { new_test_ext(1).execute_with(|| { From 2e6422d7ca36280b3d7af4208bea6031490f5e27 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 11:58:33 +0400 Subject: [PATCH 039/185] chore: fmt --- pallets/subtensor/tests/swap.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 3a2118c4a..40c9a9a2a 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -291,7 +291,7 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { let new_hotkey_1 = U256::from(2); let new_hotkey_2 = U256::from(4); let coldkey = U256::from(3); - let swap_cost = 1_000_000_000u64*2; + let swap_cost = 1_000_000_000u64 * 2; let tx_rate_limit = 1; @@ -299,10 +299,10 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); log::info!("current_tx_rate_limit: {:?}", current_tx_rate_limit); - // Set the transaction rate limit - SubtensorModule::set_tx_rate_limit(tx_rate_limit); - // assert the rate limit is set to 1000 blocks - assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); + // Set the transaction rate limit + SubtensorModule::set_tx_rate_limit(tx_rate_limit); + // assert the rate limit is set to 1000 blocks + assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); // Setup initial state add_network(netuid, tempo, 0); @@ -316,7 +316,6 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { &new_hotkey_1 )); - // Attempt to perform another swap immediately, which should fail due to rate limit assert_err!( SubtensorModule::do_swap_hotkey( @@ -327,7 +326,6 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { Error::::HotKeySetTxRateLimitExceeded ); - // move in time past the rate limit step_block(1001); assert_ok!(SubtensorModule::do_swap_hotkey( @@ -335,7 +333,6 @@ fn test_swap_hotkey_tx_rate_limit_exceeded() { &new_hotkey_1, &new_hotkey_2 )); - }); } From c595f44a5a0eb5fe2764f88fb987aa6d366ad01e Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 18 Jun 2024 14:36:08 +0400 Subject: [PATCH 040/185] chore: review comments --- pallets/subtensor/src/swap.rs | 107 ++++++++-------- pallets/subtensor/tests/swap.rs | 213 +++++++++----------------------- 2 files changed, 109 insertions(+), 211 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 0b527b935..c203f89bc 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -62,22 +62,22 @@ impl Pallet { let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); - Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight)?; - Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight)?; - Self::swap_delegates(old_hotkey, new_hotkey, &mut weight)?; - Self::swap_stake(old_hotkey, new_hotkey, &mut weight)?; + Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); + Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); + Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); + Self::swap_stake(old_hotkey, new_hotkey, &mut weight); // Store the value of is_network_member for the old key - let netuid_is_member: Vec = Self::get_netuid_is_member(old_hotkey, &mut weight)?; + let netuid_is_member: Vec = Self::get_netuid_is_member(old_hotkey, &mut weight); - Self::swap_is_network_member(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; - Self::swap_axons(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; - Self::swap_keys(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; - Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; - Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; - Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight)?; + Self::swap_is_network_member(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_axons(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_keys(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight)?; + Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); Self::set_last_tx_block(&coldkey, block); weight.saturating_accrue(T::DbWeight::get().writes(1)); @@ -100,16 +100,13 @@ impl Pallet { /// # Returns /// /// * `Result, Error>` - A vector of network IDs where the hotkey is a member. - pub fn get_netuid_is_member( - old_hotkey: &T::AccountId, - weight: &mut Weight, - ) -> Result, Error> { + pub fn get_netuid_is_member(old_hotkey: &T::AccountId, weight: &mut Weight) -> Vec { let netuid_is_member: Vec = as IterableStorageDoubleMap<_, _, _>>::iter_prefix(old_hotkey) .map(|(netuid, _)| netuid) .collect(); weight.saturating_accrue(T::DbWeight::get().reads(netuid_is_member.len() as u64)); - Ok(netuid_is_member) + netuid_is_member } /// Swaps the owner of the hotkey. @@ -129,11 +126,10 @@ impl Pallet { new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight, - ) -> DispatchResult { + ) { Owner::::remove(old_hotkey); Owner::::insert(new_hotkey, coldkey.clone()); weight.saturating_accrue(T::DbWeight::get().writes(2)); - Ok(()) } /// Swaps the total stake of the hotkey. @@ -144,20 +140,22 @@ impl Pallet { /// * `new_hotkey` - The new hotkey. /// * `weight` - The weight of the transaction. /// - /// # Returns + /// # Weight Calculation /// - /// * `Result<(), Error>` - The result of the operation. + /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. + /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). pub fn swap_total_hotkey_stake( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight, - ) -> DispatchResult { + ) { if let Ok(total_hotkey_stake) = TotalHotkeyStake::::try_get(old_hotkey) { TotalHotkeyStake::::remove(old_hotkey); TotalHotkeyStake::::insert(new_hotkey, total_hotkey_stake); - weight.saturating_accrue(T::DbWeight::get().writes(2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); } - Ok(()) } /// Swaps the delegates of the hotkey. @@ -168,20 +166,22 @@ impl Pallet { /// * `new_hotkey` - The new hotkey. /// * `weight` - The weight of the transaction. /// - /// # Returns + /// # Weight Calculation /// - /// * `Result<(), Error>` - The result of the operation. + /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. + /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). pub fn swap_delegates( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight, - ) -> DispatchResult { + ) { if let Ok(delegate_take) = Delegates::::try_get(old_hotkey) { Delegates::::remove(old_hotkey); Delegates::::insert(new_hotkey, delegate_take); - weight.saturating_accrue(T::DbWeight::get().writes(2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); } - Ok(()) } /// Swaps the stake of the hotkey. @@ -195,11 +195,7 @@ impl Pallet { /// # Returns /// /// * `Result<(), Error>` - The result of the operation. - pub fn swap_stake( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) -> DispatchResult { + pub fn swap_stake(old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight) { let mut writes = 0; let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); let stake_count = stakes.len() as u32; @@ -210,7 +206,6 @@ impl Pallet { } *weight += T::DbWeight::get().writes(writes as u64); - Ok(()) } /// Swaps the network membership status of the hotkey. /// @@ -229,14 +224,13 @@ impl Pallet { new_hotkey: &T::AccountId, netuid_is_member: &[u16], weight: &mut Weight, - ) -> DispatchResult { + ) { let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); for netuid in netuid_is_member.iter() { IsNetworkMember::::insert(new_hotkey, netuid, true); weight.saturating_accrue(T::DbWeight::get().writes(1)); } - Ok(()) } /// Swaps the axons of the hotkey. @@ -248,24 +242,27 @@ impl Pallet { /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. /// * `weight` - The weight of the transaction. /// - /// # Returns + /// # Weight Calculation /// - /// * `Result<(), Error>` - The result of the operation. + /// * Reads: 1 for each network ID if the old hotkey exists in that network. + /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). pub fn swap_axons( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, netuid_is_member: &[u16], weight: &mut Weight, - ) -> DispatchResult { + ) { for netuid in netuid_is_member.iter() { if let Ok(axon_info) = Axons::::try_get(netuid, old_hotkey) { Axons::::remove(netuid, old_hotkey); Axons::::insert(netuid, new_hotkey, axon_info); - weight.saturating_accrue(T::DbWeight::get().writes(2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); } } - Ok(()) } + /// Swaps the references in the keys storage map of the hotkey. /// /// # Arguments @@ -281,9 +278,9 @@ impl Pallet { pub fn swap_keys( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, - netuid_is_member: &Vec, + netuid_is_member: &[u16], weight: &mut Weight, - ) -> DispatchResult { + ) { let mut writes = 0; for netuid in netuid_is_member { let keys: Vec<(u16, T::AccountId)> = Keys::::iter_prefix(netuid).collect(); @@ -297,7 +294,6 @@ impl Pallet { } log::info!("writes: {:?}", writes); *weight += T::DbWeight::get().writes(writes as u64); - Ok(()) } /// Swaps the loaded emission of the hotkey. @@ -317,7 +313,7 @@ impl Pallet { new_hotkey: &T::AccountId, netuid_is_member: &[u16], weight: &mut Weight, - ) -> DispatchResult { + ) { for netuid in netuid_is_member { if let Some(mut emissions) = LoadedEmission::::get(netuid) { for emission in emissions.iter_mut() { @@ -329,7 +325,6 @@ impl Pallet { } } *weight += T::DbWeight::get().writes(netuid_is_member.len() as u64); - Ok(()) } /// Swaps the UIDs of the hotkey. @@ -349,7 +344,7 @@ impl Pallet { new_hotkey: &T::AccountId, netuid_is_member: &[u16], weight: &mut Weight, - ) -> DispatchResult { + ) { for netuid in netuid_is_member.iter() { if let Ok(uid) = Uids::::try_get(netuid, old_hotkey) { Uids::::remove(netuid, old_hotkey); @@ -357,7 +352,6 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().writes(2)); } } - Ok(()) } /// Swaps the Prometheus data of the hotkey. @@ -369,23 +363,25 @@ impl Pallet { /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. /// * `weight` - The weight of the transaction. /// - /// # Returns + /// # Weight Calculation /// - /// * `Result<(), Error>` - The result of the operation. + /// * Reads: 1 for each network ID if the old hotkey exists in that network. + /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). pub fn swap_prometheus( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, netuid_is_member: &[u16], weight: &mut Weight, - ) -> DispatchResult { + ) { for netuid in netuid_is_member.iter() { if let Ok(prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { Prometheus::::remove(netuid, old_hotkey); Prometheus::::insert(netuid, new_hotkey, prometheus_info); - weight.saturating_accrue(T::DbWeight::get().writes(2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); } } - Ok(()) } /// Swaps the total hotkey-coldkey stakes for the current interval. @@ -403,7 +399,7 @@ impl Pallet { old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight, - ) -> DispatchResult { + ) { let stakes: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); log::info!("Stakes to swap: {:?}", stakes); @@ -417,6 +413,5 @@ impl Pallet { TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove } - Ok(()) } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 40c9a9a2a..1bbe0344d 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -61,7 +61,7 @@ fn test_do_swap_hotkey_ok() { let mut weight = Weight::zero(); // UIDs - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( Uids::::get(netuid, new_hotkey), Uids::::get(netuid, old_hotkey) @@ -69,7 +69,7 @@ fn test_do_swap_hotkey_ok() { } // Prometheus - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( Prometheus::::get(netuid, new_hotkey), Prometheus::::get(netuid, old_hotkey) @@ -77,7 +77,7 @@ fn test_do_swap_hotkey_ok() { } // LoadedEmission - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( LoadedEmission::::get(netuid).unwrap(), LoadedEmission::::get(netuid).unwrap() @@ -85,7 +85,7 @@ fn test_do_swap_hotkey_ok() { } // IsNetworkMember - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert!(IsNetworkMember::::contains_key(new_hotkey, netuid)); assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); } @@ -112,7 +112,7 @@ fn test_do_swap_hotkey_ok() { ); // Axons - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight).unwrap() { + for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( Axons::::get(netuid, new_hotkey), Axons::::get(netuid, old_hotkey) @@ -215,7 +215,7 @@ fn test_do_swap_hotkey_ok_robust() { let mut weight = Weight::zero(); // UIDs for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert_eq!( Uids::::get(netuid, new_hotkeys[i]), @@ -225,7 +225,7 @@ fn test_do_swap_hotkey_ok_robust() { // Prometheus for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert_eq!( Prometheus::::get(netuid, new_hotkeys[i]), @@ -235,7 +235,7 @@ fn test_do_swap_hotkey_ok_robust() { // LoadedEmission for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert_eq!( LoadedEmission::::get(netuid).unwrap(), @@ -245,7 +245,7 @@ fn test_do_swap_hotkey_ok_robust() { // IsNetworkMember for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight).unwrap() + SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert!(IsNetworkMember::::contains_key( new_hotkeys[i], @@ -376,12 +376,7 @@ fn test_swap_owner_success() { Owner::::insert(old_hotkey, coldkey); // Perform the swap - assert_ok!(SubtensorModule::swap_owner( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight - )); + SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -401,12 +396,7 @@ fn test_swap_owner_old_hotkey_not_exist() { assert!(!Owner::::contains_key(old_hotkey)); // Perform the swap - assert_ok!(SubtensorModule::swap_owner( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight - )); + SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -428,12 +418,7 @@ fn test_swap_owner_new_hotkey_already_exists() { Owner::::insert(new_hotkey, another_coldkey); // Perform the swap - assert_ok!(SubtensorModule::swap_owner( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight - )); + SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -453,12 +438,7 @@ fn test_swap_owner_weight_update() { Owner::::insert(old_hotkey, coldkey); // Perform the swap - assert_ok!(SubtensorModule::swap_owner( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight - )); + SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the weight update let expected_weight = ::DbWeight::get().writes(2); @@ -478,11 +458,7 @@ fn test_swap_total_hotkey_stake_success() { TotalHotkeyStake::::insert(old_hotkey, total_stake); // Perform the swap - assert_ok!(SubtensorModule::swap_total_hotkey_stake( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_total_hotkey_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the swap assert_eq!(TotalHotkeyStake::::get(new_hotkey), total_stake); @@ -501,11 +477,7 @@ fn test_swap_total_hotkey_stake_old_hotkey_not_exist() { assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); // Perform the swap - assert_ok!(SubtensorModule::swap_total_hotkey_stake( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_total_hotkey_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify that new_hotkey does not have a stake assert!(!TotalHotkeyStake::::contains_key(new_hotkey)); @@ -524,14 +496,10 @@ fn test_swap_total_hotkey_stake_weight_update() { TotalHotkeyStake::::insert(old_hotkey, total_stake); // Perform the swap - assert_ok!(SubtensorModule::swap_total_hotkey_stake( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_total_hotkey_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); + let expected_weight = ::DbWeight::get().reads_writes(1, 2); assert_eq!(weight, expected_weight); }); } @@ -548,11 +516,7 @@ fn test_swap_delegates_success() { Delegates::::insert(old_hotkey, delegate_take); // Perform the swap - assert_ok!(SubtensorModule::swap_delegates( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_delegates(&old_hotkey, &new_hotkey, &mut weight); // Verify the swap assert_eq!(Delegates::::get(new_hotkey), delegate_take); @@ -571,11 +535,7 @@ fn test_swap_delegates_old_hotkey_not_exist() { assert!(!Delegates::::contains_key(old_hotkey)); // Perform the swap - assert_ok!(SubtensorModule::swap_delegates( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_delegates(&old_hotkey, &new_hotkey, &mut weight); // Verify that new_hotkey does not have a delegate assert!(!Delegates::::contains_key(new_hotkey)); @@ -594,14 +554,10 @@ fn test_swap_delegates_weight_update() { Delegates::::insert(old_hotkey, delegate_take); // Perform the swap - assert_ok!(SubtensorModule::swap_delegates( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_delegates(&old_hotkey, &new_hotkey, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); + let expected_weight = ::DbWeight::get().reads_writes(1, 2); assert_eq!(weight, expected_weight); }); } @@ -619,11 +575,7 @@ fn test_swap_stake_success() { Stake::::insert(old_hotkey, coldkey, stake_amount); // Perform the swap - assert_ok!(SubtensorModule::swap_stake( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the swap assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); @@ -647,11 +599,7 @@ fn test_swap_stake_old_hotkey_not_exist() { assert!(Stake::::contains_key(old_hotkey, coldkey)); // Perform the swap - assert_ok!(SubtensorModule::swap_stake( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify that new_hotkey has the stake and old_hotkey does not assert!(Stake::::contains_key(new_hotkey, coldkey)); @@ -672,11 +620,7 @@ fn test_swap_stake_weight_update() { Stake::::insert(old_hotkey, coldkey, stake_amount); // Perform the swap - assert_ok!(SubtensorModule::swap_stake( - &old_hotkey, - &new_hotkey, - &mut weight - )); + SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the weight update let expected_weight = ::DbWeight::get().writes(2); @@ -698,12 +642,12 @@ fn test_swap_is_network_member_success() { } // Perform the swap - assert_ok!(SubtensorModule::swap_is_network_member( + SubtensorModule::swap_is_network_member( &old_hotkey, &new_hotkey, &netuid_is_member, - &mut weight - )); + &mut weight, + ); // Verify the swap for netuid in &netuid_is_member { @@ -727,12 +671,12 @@ fn test_swap_is_network_member_weight_update() { } // Perform the swap - assert_ok!(SubtensorModule::swap_is_network_member( + SubtensorModule::swap_is_network_member( &old_hotkey, &new_hotkey, &netuid_is_member, - &mut weight - )); + &mut weight, + ); // Verify the weight update let expected_weight = ::DbWeight::get().writes(4); @@ -764,12 +708,7 @@ fn test_swap_axons_success() { } // Perform the swap - assert_ok!(SubtensorModule::swap_axons( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_axons(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the swap for netuid in &netuid_is_member { @@ -803,16 +742,12 @@ fn test_swap_axons_weight_update() { } // Perform the swap - assert_ok!(SubtensorModule::swap_axons( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_axons(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(4); - assert_eq!(weight, expected_weight); + let expected_weight = netuid_is_member.len() as u64 + * ::DbWeight::get().reads_writes(1, 2); + assert_eq!(weight, expected_weight.into()); }); } @@ -832,12 +767,7 @@ fn test_swap_keys_success() { } // Perform the swap - assert_ok!(SubtensorModule::swap_keys( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_keys(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the swap for netuid in &netuid_is_member { @@ -867,12 +797,7 @@ fn test_swap_keys_weight_update() { } // Perform the swap - assert_ok!(SubtensorModule::swap_keys( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_keys(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the weight update let expected_weight = ::DbWeight::get().writes(4); @@ -902,12 +827,12 @@ fn test_swap_loaded_emission_success() { } // Perform the swap - assert_ok!(SubtensorModule::swap_loaded_emission( + SubtensorModule::swap_loaded_emission( &old_hotkey, &new_hotkey, &netuid_is_member, - &mut weight - )); + &mut weight, + ); // Verify the swap for netuid in &netuid_is_member { @@ -941,12 +866,12 @@ fn test_swap_loaded_emission_weight_update() { } // Perform the swap - assert_ok!(SubtensorModule::swap_loaded_emission( + SubtensorModule::swap_loaded_emission( &old_hotkey, &new_hotkey, &netuid_is_member, - &mut weight - )); + &mut weight, + ); // Verify the weight update let expected_weight = ::DbWeight::get().writes(2); @@ -969,12 +894,7 @@ fn test_swap_uids_success() { } // Perform the swap - assert_ok!(SubtensorModule::swap_uids( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_uids(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the swap for netuid in &netuid_is_member { @@ -999,12 +919,7 @@ fn test_swap_uids_weight_update() { } // Perform the swap - assert_ok!(SubtensorModule::swap_uids( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_uids(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the weight update let expected_weight = ::DbWeight::get().writes(4); @@ -1033,12 +948,7 @@ fn test_swap_prometheus_success() { } // Perform the swap - assert_ok!(SubtensorModule::swap_prometheus( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_prometheus(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the swap for netuid in &netuid_is_member { @@ -1072,15 +982,11 @@ fn test_swap_prometheus_weight_update() { } // Perform the swap - assert_ok!(SubtensorModule::swap_prometheus( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight - )); + SubtensorModule::swap_prometheus(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(4); + let expected_weight = netuid_is_member.len() as u64 + * ::DbWeight::get().reads_writes(1, 2); assert_eq!(weight, expected_weight); }); } @@ -1098,12 +1004,10 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // Perform the swap - assert_ok!( - SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( - &old_hotkey, - &new_hotkey, - &mut weight - ) + SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( + &old_hotkey, + &new_hotkey, + &mut weight, ); // Verify the swap @@ -1130,12 +1034,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_weight_update() { TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // Perform the swap - assert_ok!( - SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( - &old_hotkey, - &new_hotkey, - &mut weight - ) + + SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( + &old_hotkey, + &new_hotkey, + &mut weight, ); // Verify the weight update From b9e6a26532f2d016e70ce78b6244731be49ba1b3 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 21 Jun 2024 21:49:42 +0400 Subject: [PATCH 041/185] fix: pr comments --- pallets/subtensor/src/swap.rs | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index c203f89bc..692640d34 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -99,7 +99,7 @@ impl Pallet { /// /// # Returns /// - /// * `Result, Error>` - A vector of network IDs where the hotkey is a member. + /// * `Vec` - A vector of network IDs where the hotkey is a member. pub fn get_netuid_is_member(old_hotkey: &T::AccountId, weight: &mut Weight) -> Vec { let netuid_is_member: Vec = as IterableStorageDoubleMap<_, _, _>>::iter_prefix(old_hotkey) @@ -118,9 +118,6 @@ impl Pallet { /// * `coldkey` - The coldkey owning the hotkey. /// * `weight` - The weight of the transaction. /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_owner( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, @@ -191,22 +188,23 @@ impl Pallet { /// * `old_hotkey` - The old hotkey. /// * `new_hotkey` - The new hotkey. /// * `weight` - The weight of the transaction. - /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_stake(old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight) { let mut writes = 0; let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); let stake_count = stakes.len() as u32; + for (coldkey, stake_amount) in stakes { Stake::::insert(new_hotkey, &coldkey, stake_amount); - let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); - writes += 2; // One write for insert and one for remove + writes += 1; // One write for insert } + + // Clear the prefix for the old hotkey after transferring all stakes + let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); + writes += 1; // One write for clear_prefix *weight += T::DbWeight::get().writes(writes as u64); } + /// Swaps the network membership status of the hotkey. /// /// # Arguments @@ -215,10 +213,6 @@ impl Pallet { /// * `new_hotkey` - The new hotkey. /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. /// * `weight` - The weight of the transaction. - /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_is_network_member( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, @@ -271,10 +265,6 @@ impl Pallet { /// * `new_hotkey` - The new hotkey. /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. /// * `weight` - The weight of the transaction. - /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_keys( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, @@ -305,9 +295,6 @@ impl Pallet { /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. /// * `weight` - The weight of the transaction. /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_loaded_emission( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, @@ -336,9 +323,6 @@ impl Pallet { /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. /// * `weight` - The weight of the transaction. /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_uids( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, @@ -392,9 +376,6 @@ impl Pallet { /// * `new_hotkey` - The new hotkey. /// * `weight` - The weight of the transaction. /// - /// # Returns - /// - /// * `Result<(), Error>` - The result of the operation. pub fn swap_total_hotkey_coldkey_stakes_this_interval( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, From 8626ca343fd55b0f76cc306572ead068181a1479 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 21 Jun 2024 21:49:59 +0400 Subject: [PATCH 042/185] chore: lint --- pallets/subtensor/src/swap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 692640d34..4f8097d26 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -192,12 +192,12 @@ impl Pallet { let mut writes = 0; let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); let stake_count = stakes.len() as u32; - + for (coldkey, stake_amount) in stakes { Stake::::insert(new_hotkey, &coldkey, stake_amount); writes += 1; // One write for insert } - + // Clear the prefix for the old hotkey after transferring all stakes let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); writes += 1; // One write for clear_prefix From 2fc9e3193f70dbea2f9ffadf070f15de32ca237f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sat, 22 Jun 2024 00:48:02 +0400 Subject: [PATCH 043/185] fix: remove unused function --- pallets/subtensor/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7ca2a2777..64aefcfee 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -275,11 +275,6 @@ pub mod pallet { pub fn TotalSupply() -> u64 { 21_000_000_000_000_000 // Rao => 21_000_000 Tao } - /// Hotkey swap cost. - #[pallet::type_value] - pub fn HotkeySwapCost() -> u64 { - 1_000_000_000 - } /// Default total stake. #[pallet::type_value] pub fn DefaultDefaultTake() -> u16 { From 7518c5560910f61737b157601f4ca9bfcf961fe1 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 21 Jun 2024 17:34:01 -0400 Subject: [PATCH 044/185] clippy fix --- pallets/subtensor/tests/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 1bbe0344d..2a12ed312 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -747,7 +747,7 @@ fn test_swap_axons_weight_update() { // Verify the weight update let expected_weight = netuid_is_member.len() as u64 * ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight.into()); + assert_eq!(weight, expected_weight); }); } From cadbe595ad978ed579f9ae040dbe210258640a00 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 2 Jul 2024 23:33:46 +0400 Subject: [PATCH 045/185] chore: conflicts, lints --- justfile | 8 ++++++-- pallets/subtensor/src/swap.rs | 21 +++++++++++---------- pallets/subtensor/tests/mock.rs | 2 +- pallets/subtensor/tests/swap.rs | 2 ++ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/justfile b/justfile index 0a02ee7d3..a75b0052b 100644 --- a/justfile +++ b/justfile @@ -35,6 +35,11 @@ clippy-fix: -A clippy::todo \ -A clippy::unimplemented \ -A clippy::indexing_slicing + @echo "Running cargo clippy with automatic fixes on potentially dirty code..." + cargo +{{RUSTV}} clippy --fix --allow-dirty --workspace --all-targets -- \ + -A clippy::todo \ + -A clippy::unimplemented \ + -A clippy::indexing_slicing fix: @echo "Running cargo fix..." cargo +{{RUSTV}} fix --workspace @@ -46,5 +51,4 @@ lint: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." just clippy-fix @echo "Running cargo clippy..." - just clippy - + just clippy \ No newline at end of file diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 4f8097d26..ce090d736 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -49,8 +49,9 @@ impl Pallet { Error::::HotKeySetTxRateLimitExceeded ); - weight - .saturating_accrue(T::DbWeight::get().reads((TotalNetworks::::get() + 1u16) as u64)); + weight.saturating_accrue( + T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), + ); let swap_cost = Self::get_hotkey_swap_cost(); log::debug!("Swap cost: {:?}", swap_cost); @@ -189,20 +190,20 @@ impl Pallet { /// * `new_hotkey` - The new hotkey. /// * `weight` - The weight of the transaction. pub fn swap_stake(old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight) { - let mut writes = 0; + let mut writes: u64 = 0; let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); let stake_count = stakes.len() as u32; for (coldkey, stake_amount) in stakes { Stake::::insert(new_hotkey, &coldkey, stake_amount); - writes += 1; // One write for insert + writes = writes.saturating_add(1u64); // One write for insert } // Clear the prefix for the old hotkey after transferring all stakes let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); - writes += 1; // One write for clear_prefix + writes = writes.saturating_add(1); // One write for insert; // One write for clear_prefix - *weight += T::DbWeight::get().writes(writes as u64); + weight.saturating_accrue(T::DbWeight::get().writes(writes)); } /// Swaps the network membership status of the hotkey. @@ -271,7 +272,7 @@ impl Pallet { netuid_is_member: &[u16], weight: &mut Weight, ) { - let mut writes = 0; + let mut writes: u64 = 0; for netuid in netuid_is_member { let keys: Vec<(u16, T::AccountId)> = Keys::::iter_prefix(netuid).collect(); for (uid, key) in keys { @@ -279,11 +280,11 @@ impl Pallet { log::info!("old hotkey found: {:?}", old_hotkey); Keys::::insert(netuid, uid, new_hotkey.clone()); } - writes += 2; + writes = writes.saturating_add(2u64); } } log::info!("writes: {:?}", writes); - *weight += T::DbWeight::get().writes(writes as u64); + weight.saturating_accrue(T::DbWeight::get().writes(writes)); } /// Swaps the loaded emission of the hotkey. @@ -311,7 +312,7 @@ impl Pallet { LoadedEmission::::insert(netuid, emissions); } } - *weight += T::DbWeight::get().writes(netuid_is_member.len() as u64); + weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); } /// Swaps the UIDs of the hotkey. diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index cf58f49d2..d4f5940b8 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -3,7 +3,7 @@ use frame_support::derive_impl; use frame_support::dispatch::DispatchResultWithPostInfo; use frame_support::weights::constants::RocksDbWeight; use frame_support::{ - assert_ok, derive_impl, + assert_ok, dispatch::DispatchResultWithPostInfo, parameter_types, traits::{Everything, Hooks}, diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 2a12ed312..af7d19d2d 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1,3 +1,5 @@ +#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] + use codec::Encode; use frame_support::weights::Weight; use frame_support::{assert_err, assert_ok}; From 2dda611455da8fd9e7a295ac635b04d0ea862de6 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 2 Jul 2024 23:43:50 +0400 Subject: [PATCH 046/185] chore: lints --- pallets/subtensor/tests/mock.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index d4f5940b8..3e7e74f95 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -3,9 +3,7 @@ use frame_support::derive_impl; use frame_support::dispatch::DispatchResultWithPostInfo; use frame_support::weights::constants::RocksDbWeight; use frame_support::{ - assert_ok, - dispatch::DispatchResultWithPostInfo, - parameter_types, + assert_ok, parameter_types, traits::{Everything, Hooks}, weights, }; From 28872d69b34399e7aa1782eff7fa18121e62913a Mon Sep 17 00:00:00 2001 From: Keith Date: Thu, 13 Jun 2024 21:02:40 +0900 Subject: [PATCH 047/185] Implement safe mode pallet --- Cargo.lock | 1097 +++++++++++++++++++++++--------------------- Cargo.toml | 1 + runtime/Cargo.toml | 7 + runtime/src/lib.rs | 52 ++- 4 files changed, 634 insertions(+), 523 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5fc4c0ba..9ed171d9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,11 +23,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ - "gimli 0.28.1", + "gimli 0.29.0", ] [[package]] @@ -68,7 +68,7 @@ dependencies = [ "cipher 0.4.4", "ctr", "ghash", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -77,7 +77,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -136,47 +136,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -184,9 +185,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "approx" @@ -204,7 +205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.5", "proc-macro-error", "proc-macro2", "quote", @@ -218,11 +219,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.5", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -311,7 +312,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits", "rayon", "zeroize", @@ -379,7 +380,7 @@ dependencies = [ "ark-std", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -520,9 +521,9 @@ checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" [[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" @@ -588,9 +589,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ "async-lock", "cfg-if", @@ -599,7 +600,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.32", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -607,24 +608,24 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite 0.2.14", ] [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -642,22 +643,22 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ - "addr2line 0.21.0", + "addr2line 0.22.0", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.32.2", + "object 0.36.0", "rustc-demangle", ] @@ -744,13 +745,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.17", + "prettyplease 0.2.20", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -923,9 +924,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -962,9 +963,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" dependencies = [ "serde", ] @@ -986,7 +987,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -994,12 +995,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.91" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -1013,9 +1015,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", ] @@ -1068,9 +1070,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1078,7 +1080,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1116,9 +1118,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1127,9 +1129,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -1137,34 +1139,34 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "codespan-reporting" @@ -1178,9 +1180,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "comfy-table" @@ -1189,7 +1191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "strum 0.26.2", - "strum_macros 0.26.2", + "strum_macros 0.26.4", "unicode-width", ] @@ -1217,9 +1219,9 @@ checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1258,7 +1260,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -1415,7 +1417,7 @@ dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "itertools", + "itertools 0.10.5", "log", "smallvec", "wasmparser", @@ -1424,9 +1426,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -1452,9 +1454,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -1470,7 +1472,7 @@ checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -1502,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -1523,24 +1525,23 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version 0.4.0", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -1552,14 +1553,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "cxx" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dc7287237dd438b926a81a1a5605dad33d286870e5eee2db17bf2bcd9e92a" +checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" dependencies = [ "cc", "cxxbridge-flags", @@ -1569,9 +1570,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47c6c8ad7c1a10d3ef0fe3ff6733f4db0d78f08ef0b13121543163ef327058b" +checksum = "d8b2766fbd92be34e9ed143898fce6c572dc009de39506ed6903e5a05b68914e" dependencies = [ "cc", "codespan-reporting", @@ -1579,31 +1580,31 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "cxxbridge-flags" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701a1ac7a697e249cdd8dc026d7a7dafbfd0dbcd8bd24ec55889f2bc13dd6287" +checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" [[package]] name = "cxxbridge-macro" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b404f596046b0bb2d903a9c786b875a126261b52b7c3a64bbb66382c41c771df" +checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -1611,27 +1612,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 2.0.58", + "strsim", + "syn 2.0.67", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -1641,23 +1642,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1665,9 +1666,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -1737,20 +1738,20 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[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", "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 1.0.109", + "syn 2.0.67", ] [[package]] @@ -1786,7 +1787,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -1833,13 +1834,13 @@ 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", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -1879,9 +1880,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.58", + "syn 2.0.67", "termcolor", - "toml 0.8.13", + "toml 0.8.14", "walkdir", ] @@ -1955,12 +1956,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519", "rand_core 0.6.4", "serde", "sha2 0.10.8", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -1980,9 +1981,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -2000,7 +2001,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -2024,22 +2025,22 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2069,9 +2070,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2112,9 +2113,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -2123,11 +2124,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 4.0.3", + "event-listener 5.3.1", "pin-project-lite 0.2.14", ] @@ -2142,16 +2143,17 @@ dependencies = [ [[package]] name = "expander" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e83c02035136f1592a47964ea60c05a50e4ed8b5892cfac197063850898d4d" +checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2" dependencies = [ "blake2 0.10.6", + "file-guard", "fs-err", - "prettier-please", + "prettyplease 0.2.20", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2168,9 +2170,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdlimit" @@ -2189,7 +2191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -2207,9 +2209,19 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.7" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "file-guard" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "file-per-thread-logger" @@ -2245,7 +2257,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "scale-info", ] @@ -2269,9 +2281,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-sys", @@ -2347,7 +2359,7 @@ version = "32.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "Inflector", - "array-bytes 6.2.2", + "array-bytes 6.2.3", "chrono", "clap", "comfy-table", @@ -2356,7 +2368,7 @@ dependencies = [ "frame-system", "gethostname", "handlebars", - "itertools", + "itertools 0.10.5", "lazy_static", "linked-hash-map", "log", @@ -2426,7 +2438,7 @@ version = "28.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aquamarine 0.5.0", - "array-bytes 6.2.2", + "array-bytes 6.2.3", "bitflags 1.3.2", "docify", "environmental", @@ -2471,13 +2483,13 @@ dependencies = [ "derive-syn-parse 0.2.0", "expander", "frame-support-procedural-tools", - "itertools", + "itertools 0.10.5", "macro_magic", "proc-macro-warning", "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2489,7 +2501,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2499,7 +2511,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2650,7 +2662,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2752,9 +2764,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -2802,6 +2814,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "glob" version = "0.3.1" @@ -2820,7 +2838,7 @@ dependencies = [ "futures-timer", "no-std-compat", "nonzero_ext", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "portable-atomic", "quanta", "rand", @@ -2836,7 +2854,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -2907,9 +2925,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2921,7 +2939,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -2942,6 +2960,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" @@ -3049,9 +3073,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -3067,9 +3091,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -3082,7 +3106,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.14", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -3099,7 +3123,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-native-certs", "tokio", "tokio-rustls", @@ -3224,18 +3248,18 @@ dependencies = [ [[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", "quote", @@ -3259,7 +3283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3273,9 +3297,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -3295,7 +3319,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -3312,7 +3336,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.6", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -3330,11 +3354,17 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -3344,6 +3374,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -3352,9 +3391,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -3394,7 +3433,7 @@ dependencies = [ "futures-util", "hyper", "jsonrpsee-types", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "rustc-hash", "serde", @@ -3414,7 +3453,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -3499,7 +3538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -3510,7 +3549,7 @@ checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" dependencies = [ "kvdb", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "regex", "rocksdb", "smallvec", @@ -3530,9 +3569,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -3541,7 +3580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3559,7 +3598,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.14", + "getrandom 0.2.15", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -3624,7 +3663,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project", "quick-protobuf", "rand", @@ -3644,7 +3683,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "smallvec", "trust-dns-resolver", ] @@ -3806,7 +3845,7 @@ dependencies = [ "libp2p-identity", "libp2p-tls", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "quinn-proto", "rand", "rustls 0.20.9", @@ -3922,7 +3961,7 @@ dependencies = [ "futures-rustls", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "quicksink", "rw-stream-sink", "soketto", @@ -3995,7 +4034,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -4018,9 +4057,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.16" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" dependencies = [ "cc", "pkg-config", @@ -4068,9 +4107,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -4086,9 +4125,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4129,9 +4168,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "d6eab492fe7f8651add23237ea56dbf11b3c4ff762ab83d40a47f11433421f91" dependencies = [ "libc", "lz4-sys", @@ -4139,9 +4178,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "e9764018d143cc854c9f17f0b907de70f14393b1f502da6375dce70f00514eb3" dependencies = [ "cc", "libc", @@ -4165,7 +4204,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4179,7 +4218,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4190,7 +4229,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4201,7 +4240,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4246,9 +4285,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memfd" @@ -4256,7 +4295,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.32", + "rustix 0.38.34", ] [[package]] @@ -4315,9 +4354,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -4344,16 +4383,16 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "either", "hashlink", "lioness", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "rand_chacha", "rand_distr", - "subtle 2.4.1", + "subtle 2.6.0", "thiserror", "zeroize", ] @@ -4468,9 +4507,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.5" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea4908d4f23254adda3daa60ffef0f1ac7b8c3e9a864cf3cc154b251908a2ef" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" dependencies = [ "approx", "matrixmultiply", @@ -4678,6 +4717,7 @@ dependencies = [ "pallet-preimage", "pallet-proxy", "pallet-registry", + "pallet-safe-mode", "pallet-scheduler", "pallet-subtensor", "pallet-sudo", @@ -4748,20 +4788,19 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -4793,20 +4832,19 @@ 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-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -4818,7 +4856,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -4843,6 +4881,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -5106,6 +5153,25 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] +[[package]] +name = "pallet-safe-mode" +version = "9.0.0" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-proxy", + "pallet-utility", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", +] + [[package]] name = "pallet-scheduler" version = "29.0.0" @@ -5302,7 +5368,7 @@ dependencies = [ "log", "lz4", "memmap2 0.5.10", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "siphasher", "snap", @@ -5354,7 +5420,7 @@ dependencies = [ "impl-trait-for-tuples", "lru 0.8.1", "parity-util-mem-derive", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "primitive-types", "smallvec", "winapi", @@ -5396,12 +5462,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -5420,15 +5486,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -5445,14 +5511,14 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbkdf2" @@ -5487,9 +5553,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -5498,9 +5564,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -5508,22 +5574,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "pest_meta" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -5532,9 +5598,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.2.6", @@ -5557,7 +5623,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5594,12 +5660,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "polkavm" version = "0.9.3" @@ -5649,7 +5709,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5659,7 +5719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5669,7 +5729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ "gimli 0.28.1", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "log", "object 0.32.2", "polkavm-common", @@ -5685,15 +5745,15 @@ checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" [[package]] name = "polling" -version = "3.6.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite 0.2.14", - "rustix 0.38.32", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -5747,7 +5807,7 @@ checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", - "itertools", + "itertools 0.10.5", "normalize-line-endings", "predicates-core", "regex", @@ -5769,21 +5829,11 @@ dependencies = [ "termtree", ] -[[package]] -name = "prettier-please" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" -dependencies = [ - "proc-macro2", - "syn 2.0.58", -] - [[package]] name = "prettyplease" -version = "0.1.11" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ "proc-macro2", "syn 1.0.109", @@ -5791,12 +5841,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5864,29 +5914,29 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[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.1", + "parking_lot 0.12.3", "thiserror", ] @@ -5898,7 +5948,7 @@ checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" dependencies = [ "dtoa", "itoa", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "prometheus-client-derive-encode", ] @@ -5910,7 +5960,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5941,12 +5991,12 @@ checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck 0.4.1", - "itertools", + "itertools 0.10.5", "lazy_static", "log", "multimap", "petgraph", - "prettyplease 0.1.11", + "prettyplease 0.1.25", "prost 0.11.9", "prost-types", "regex", @@ -5962,7 +6012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -5975,10 +6025,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -6073,9 +6123,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -6122,7 +6172,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", ] [[package]] @@ -6209,35 +6259,44 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "libredox", "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -6267,14 +6326,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -6288,13 +6347,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -6305,9 +6364,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "resolv-conf" @@ -6326,7 +6385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -6368,7 +6427,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -6439,9 +6498,9 @@ dependencies = [ [[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" @@ -6470,7 +6529,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver 1.0.23", ] [[package]] @@ -6498,14 +6557,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] @@ -6523,9 +6582,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -6566,9 +6625,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rw-stream-sink" @@ -6583,9 +6642,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-mix" @@ -6598,9 +6657,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", ] @@ -6667,7 +6726,7 @@ name = "sc-chain-spec" version = "27.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "docify", "log", "memmap2 0.9.4", @@ -6696,7 +6755,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -6704,12 +6763,12 @@ name = "sc-cli" version = "0.36.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "chrono", "clap", "fdlimit", "futures", - "itertools", + "itertools 0.10.5", "libp2p-identity", "log", "names", @@ -6749,7 +6808,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -6780,7 +6839,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-state-db", "schnellru", @@ -6804,7 +6863,7 @@ dependencies = [ "libp2p-identity", "log", "mockall", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-utils", "serde", @@ -6853,7 +6912,7 @@ version = "0.19.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ahash 0.8.11", - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-trait", "dyn-clone", "finality-grandpa", @@ -6862,7 +6921,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "sc-block-builder", "sc-chain-spec", @@ -6939,7 +6998,7 @@ version = "0.32.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", @@ -6989,7 +7048,7 @@ dependencies = [ "cfg-if", "libc", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rustix 0.36.17", "sc-allocator", "sc-executor-common", @@ -7020,8 +7079,8 @@ name = "sc-keystore" version = "25.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", - "parking_lot 0.12.1", + "array-bytes 6.2.3", + "parking_lot 0.12.3", "serde_json", "sp-application-crypto", "sp-core", @@ -7045,7 +7104,7 @@ dependencies = [ "mixnet", "multiaddr", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-network", "sc-transaction-pool-api", @@ -7063,7 +7122,7 @@ name = "sc-network" version = "0.34.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-channel", "async-trait", "asynchronous-codec", @@ -7078,7 +7137,7 @@ dependencies = [ "log", "mockall", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "partial_sort", "pin-project", "rand", @@ -7162,7 +7221,7 @@ name = "sc-network-light" version = "0.33.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-channel", "futures", "libp2p-identity", @@ -7183,7 +7242,7 @@ name = "sc-network-sync" version = "0.33.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-channel", "async-trait", "fork-tree", @@ -7219,7 +7278,7 @@ name = "sc-network-transactions" version = "0.33.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "futures", "libp2p", "log", @@ -7238,7 +7297,7 @@ name = "sc-offchain" version = "29.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "bytes", "fnv", "futures", @@ -7250,7 +7309,7 @@ dependencies = [ "num_cpus", "once_cell", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "sc-client-api", "sc-network", @@ -7285,7 +7344,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -7351,14 +7410,14 @@ name = "sc-rpc-spec-v2" version = "0.34.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "futures", "futures-util", "hex", "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "sc-chain-spec", "sc-client-api", @@ -7390,7 +7449,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project", "rand", "sc-chain-spec", @@ -7448,7 +7507,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sp-core", ] @@ -7482,7 +7541,7 @@ dependencies = [ "futures", "libp2p", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project", "rand", "sc-utils", @@ -7504,7 +7563,7 @@ dependencies = [ "libc", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "regex", "rustc-hash", "sc-client-api", @@ -7530,7 +7589,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -7544,7 +7603,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-transaction-pool-api", "sc-utils", @@ -7586,16 +7645,16 @@ dependencies = [ "futures-timer", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "prometheus", "sp-arithmetic", ] [[package]] name = "scale-info" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", @@ -7607,11 +7666,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -7628,9 +7687,9 @@ dependencies = [ [[package]] name = "schnellru" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" dependencies = [ "ahash 0.8.11", "cfg-if", @@ -7646,13 +7705,13 @@ dependencies = [ "aead", "arrayref", "arrayvec", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -7689,7 +7748,7 @@ dependencies = [ "generic-array 0.14.7", "pkcs8", "serdect", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -7722,11 +7781,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -7735,9 +7794,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -7763,9 +7822,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -7778,9 +7837,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -7805,13 +7864,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -7859,7 +7918,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -7936,9 +7995,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[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", ] @@ -8014,12 +8073,12 @@ dependencies = [ "aes-gcm", "blake2 0.10.6", "chacha20poly1305", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring 0.17.8", "rustc_version 0.4.0", "sha2 0.10.8", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -8034,9 +8093,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -8092,7 +8151,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8159,7 +8218,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "schnellru", "sp-api", "sp-consensus", @@ -8233,7 +8292,7 @@ name = "sp-core" version = "28.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "bandersnatch_vrfs", "bitflags 1.3.2", "blake2 0.10.6", @@ -8245,14 +8304,14 @@ dependencies = [ "hash-db", "hash256-std-hasher", "impl-serde", - "itertools", + "itertools 0.10.5", "k256", "libsecp256k1", "log", "merlin", "parity-bip39", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "paste", "primitive-types", "rand", @@ -8278,7 +8337,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -8315,7 +8374,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8324,7 +8383,7 @@ version = "10.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -8334,17 +8393,17 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8360,7 +8419,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "environmental", "parity-scale-codec", @@ -8432,7 +8491,7 @@ version = "0.34.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sp-core", "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] @@ -8543,7 +8602,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8569,20 +8628,20 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "Inflector", "expander", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8620,7 +8679,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "smallvec", "sp-core", @@ -8638,7 +8697,7 @@ version = "10.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aes-gcm", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -8664,7 +8723,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" [[package]] name = "sp-storage" @@ -8681,7 +8740,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8716,7 +8775,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "parity-scale-codec", "tracing", @@ -8758,7 +8817,7 @@ dependencies = [ "memory-db", "nohash-hasher", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "scale-info", "schnellru", @@ -8795,7 +8854,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8813,7 +8872,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "impl-trait-for-tuples", "log", @@ -8920,12 +8979,6 @@ dependencies = [ "syn 1.0.109", ] -[[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" @@ -8944,7 +8997,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "strum_macros 0.26.2", + "strum_macros 0.26.4", ] [[package]] @@ -8962,15 +9015,15 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9046,7 +9099,7 @@ dependencies = [ "sp-maybe-compressed-blob", "strum 0.26.2", "tempfile", - "toml 0.8.13", + "toml 0.8.14", "walkdir", "wasm-opt", ] @@ -9084,9 +9137,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" [[package]] name = "syn" @@ -9101,9 +9154,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" dependencies = [ "proc-macro2", "quote", @@ -9163,7 +9216,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.32", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -9182,7 +9235,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.32", + "rustix 0.38.34", "windows-sys 0.48.0", ] @@ -9194,22 +9247,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9249,9 +9302,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -9270,9 +9323,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -9304,32 +9357,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite 0.2.14", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9338,7 +9391,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.12", "tokio", ] @@ -9356,9 +9409,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -9366,7 +9419,6 @@ dependencies = [ "futures-sink", "pin-project-lite 0.2.14", "tokio", - "tracing", ] [[package]] @@ -9380,14 +9432,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.13", + "toml_edit 0.22.14", ] [[package]] @@ -9412,15 +9464,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.8", + "winnow 0.6.13", ] [[package]] @@ -9488,7 +9540,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9643,7 +9695,7 @@ dependencies = [ "ipconfig", "lazy_static", "lru-cache", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "resolv-conf", "smallvec", "thiserror", @@ -9732,9 +9784,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -9749,7 +9801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -9778,9 +9830,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna 0.5.0", @@ -9789,9 +9841,9 @@ dependencies = [ [[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 = "valuable" @@ -9819,9 +9871,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", @@ -9893,7 +9945,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", "wasm-bindgen-shared", ] @@ -9927,7 +9979,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10245,14 +10297,14 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.32", + "rustix 0.38.34", ] [[package]] name = "wide" -version = "0.7.15" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89beec544f246e679fc25490e3f8e08003bc4bf612068f325120dad4cea02c1c" +checksum = "8a040b111774ab63a19ef46bbc149398ab372b4ccdcfd719e9814dbd7dfd76c8" dependencies = [ "bytemuck", "safe_arch", @@ -10282,11 +10334,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -10320,7 +10372,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -10347,7 +10399,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -10382,17 +10434,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -10409,9 +10462,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -10427,9 +10480,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -10445,9 +10498,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -10463,9 +10522,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -10481,9 +10540,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -10499,9 +10558,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -10517,9 +10576,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -10532,9 +10591,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -10575,7 +10634,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -10608,7 +10667,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "static_assertions", ] @@ -10624,29 +10683,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -10659,7 +10718,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -10702,9 +10761,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 56e40c924..8a860e769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", ta pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index c2d26973b..b572aecee 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -73,6 +73,10 @@ pallet-proxy = { workspace = true } pallet-scheduler = { workspace = true } pallet-preimage = { workspace = true } +# Safe mode pallet + +pallet-safe-mode = { workspace = true } + # Used for the node subtensor's RPCs frame-system-rpc-runtime-api = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } @@ -115,6 +119,7 @@ std = [ "pallet-balances/std", "pallet-grandpa/std", "pallet-insecure-randomness-collective-flip/std", + "pallet-safe-mode/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", @@ -159,6 +164,7 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-safe-mode/runtime-benchmarks", "pallet-subtensor/runtime-benchmarks", "pallet-collective/runtime-benchmarks", "pallet-membership/runtime-benchmarks", @@ -184,6 +190,7 @@ try-runtime = [ "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", + "pallet-safe-mode/try-runtime", "pallet-subtensor/try-runtime", "pallet-collective/try-runtime", "pallet-membership/try-runtime", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5564dca5d..f5f5d3370 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -14,9 +14,9 @@ use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, pallet_prelude::{DispatchError, Get}, - traits::{fungible::HoldConsideration, LinearStoragePrice}, + traits::{fungible::HoldConsideration, Contains, LinearStoragePrice}, }; -use frame_system::{EnsureNever, EnsureRoot, RawOrigin}; +use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess, RawOrigin}; use pallet_commitments::CanCommit; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, @@ -193,7 +193,7 @@ parameter_types! { impl frame_system::Config for Runtime { // The basic call filter to use in dispatchable. - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = SafeMode; // Block & extrinsics weights: base values and limits. type BlockWeights = BlockWeights; // The maximum length of a block (in bytes). @@ -284,6 +284,49 @@ impl pallet_utility::Config for Runtime { type WeightInfo = pallet_utility::weights::SubstrateWeight; } +parameter_types! { + pub const DisallowPermissionlessEnterDuration: BlockNumber = 0; + pub const DisallowPermissionlessExtendDuration: BlockNumber = 0; + + pub const RootEnterDuration: BlockNumber = 5 * 60 * 3; // 3 hours + pub const RootExtendDuration: BlockNumber = 5 * 60 * 3; // 3 hours + + pub const DisallowPermissionlessEntering: Option = None; + pub const DisallowPermissionlessExtending: Option = None; + pub const DisallowPermissionlessRelease: Option = None; +} + +pub struct SafeModeWhitelistedCalls; +impl Contains for SafeModeWhitelistedCalls { + fn contains(call: &RuntimeCall) -> bool { + matches!( + call, + RuntimeCall::Sudo(_) + | RuntimeCall::System(_) + | RuntimeCall::SafeMode(_) + | RuntimeCall::Timestamp(_) + ) + } +} + +impl pallet_safe_mode::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; + type WhitelistedCalls = SafeModeWhitelistedCalls; + type EnterDuration = DisallowPermissionlessEnterDuration; + type ExtendDuration = DisallowPermissionlessExtendDuration; + type EnterDepositAmount = DisallowPermissionlessEntering; + type ExtendDepositAmount = DisallowPermissionlessExtending; + type ForceEnterOrigin = EnsureRootWithSuccess; + type ForceExtendOrigin = EnsureRootWithSuccess; + type ForceExitOrigin = EnsureRoot; + type ForceDepositOrigin = EnsureRoot; + type Notify = (); + type ReleaseDelay = DisallowPermissionlessRelease; + type WeightInfo = pallet_safe_mode::weights::SubstrateWeight; +} + // Existential deposit. pub const EXISTENTIAL_DEPOSIT: u64 = 500; @@ -1177,7 +1220,8 @@ construct_runtime!( Proxy: pallet_proxy, Registry: pallet_registry, Commitments: pallet_commitments, - AdminUtils: pallet_admin_utils + AdminUtils: pallet_admin_utils, + SafeMode: pallet_safe_mode, } ); From 76adc8f48f10810db602f136fa6fb7bdaa9d512a Mon Sep 17 00:00:00 2001 From: Keith Date: Tue, 2 Jul 2024 22:38:29 +0200 Subject: [PATCH 048/185] Bump spec version and safe mode enter duration --- runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f5f5d3370..6dccb5a42 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -135,7 +135,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 153, + spec_version: 154, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -288,7 +288,7 @@ parameter_types! { pub const DisallowPermissionlessEnterDuration: BlockNumber = 0; pub const DisallowPermissionlessExtendDuration: BlockNumber = 0; - pub const RootEnterDuration: BlockNumber = 5 * 60 * 3; // 3 hours + pub const RootEnterDuration: BlockNumber = 5 * 60 * 24; // 24 hours pub const RootExtendDuration: BlockNumber = 5 * 60 * 3; // 3 hours pub const DisallowPermissionlessEntering: Option = None; From 8ff7e9ceb45179ab2d61c4397ff411cc5d18a7be Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 19:00:55 -0400 Subject: [PATCH 049/185] whitelist subtensor calls during safe mode --- runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 6dccb5a42..297bd5f1b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -305,6 +305,7 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) + | RuntimeCall::SubtensorModule(_) ) } } From 0db5ad4fb0c708392cc1592cc580657182b455e5 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 19:32:07 -0400 Subject: [PATCH 050/185] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 297bd5f1b..0d9059e18 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -135,7 +135,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 154, + spec_version: 155, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 6dbcbffd66f25cb42bcf1914f38687cf46ea3dd2 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 19:43:11 -0400 Subject: [PATCH 051/185] whitelist commit_weights and add_stake --- runtime/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0d9059e18..33729112a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -305,7 +305,14 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) - | RuntimeCall::SubtensorModule(_) + | RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_weights { + netuid, + commit_hash + }) + | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { + hotkey, + amount_staked + }) ) } } From 81431053132f22dde3a1d0f29215ef566cddcf11 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 19:44:02 -0400 Subject: [PATCH 052/185] remove commit weights --- runtime/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 33729112a..9e5f81ac9 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -305,10 +305,6 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) - | RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_weights { - netuid, - commit_hash - }) | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { hotkey, amount_staked From 8130bc8294026e40481f194f6d96cab483b39974 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 2 Jul 2024 19:48:02 -0400 Subject: [PATCH 053/185] whoops --- runtime/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9e5f81ac9..2feddca5c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -305,10 +305,7 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) - | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { - hotkey, - amount_staked - }) + | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { .. }) ) } } From d79f73ba42ed7e8f8d7929794d1dd631b4c1cc60 Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 3 Jul 2024 17:34:36 +0200 Subject: [PATCH 054/185] Whitelist the majority of the extrinsics in subtensor pallet and multisig --- runtime/src/lib.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2feddca5c..1c546ec16 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -135,7 +135,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 155, + spec_version: 156, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -302,10 +302,30 @@ impl Contains for SafeModeWhitelistedCalls { matches!( call, RuntimeCall::Sudo(_) + | RuntimeCall::Multisig(_) | RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) - | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { .. }) + | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { .. } + | pallet_subtensor::Call::become_delegate { .. } + | pallet_subtensor::Call::burned_register { .. } + | pallet_subtensor::Call::commit_weights { .. } + | pallet_subtensor::Call::decrease_take { .. } + | pallet_subtensor::Call::faucet { .. } + | pallet_subtensor::Call::increase_take { .. } + | pallet_subtensor::Call::register { .. } + | pallet_subtensor::Call::register_network { .. } + | pallet_subtensor::Call::remove_stake { .. } + | pallet_subtensor::Call::reveal_weights { .. } + | pallet_subtensor::Call::root_register { .. } + | pallet_subtensor::Call::serve_axon { .. } + | pallet_subtensor::Call::serve_prometheus { .. } + | pallet_subtensor::Call::set_root_weights { .. } + | pallet_subtensor::Call::set_weights { .. } + | pallet_subtensor::Call::sudo { .. } + | pallet_subtensor::Call::sudo_unchecked_weight { .. } + | pallet_subtensor::Call::swap_hotkey { .. } + | pallet_subtensor::Call::vote { .. }) ) } } From 8128a7a5140be89e92d05d342868760f43ee7a63 Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 3 Jul 2024 19:04:28 +0200 Subject: [PATCH 055/185] cargo fmt --- runtime/src/lib.rs | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1c546ec16..759084a41 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -306,26 +306,28 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) - | RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { .. } - | pallet_subtensor::Call::become_delegate { .. } - | pallet_subtensor::Call::burned_register { .. } - | pallet_subtensor::Call::commit_weights { .. } - | pallet_subtensor::Call::decrease_take { .. } - | pallet_subtensor::Call::faucet { .. } - | pallet_subtensor::Call::increase_take { .. } - | pallet_subtensor::Call::register { .. } - | pallet_subtensor::Call::register_network { .. } - | pallet_subtensor::Call::remove_stake { .. } - | pallet_subtensor::Call::reveal_weights { .. } - | pallet_subtensor::Call::root_register { .. } - | pallet_subtensor::Call::serve_axon { .. } - | pallet_subtensor::Call::serve_prometheus { .. } - | pallet_subtensor::Call::set_root_weights { .. } - | pallet_subtensor::Call::set_weights { .. } - | pallet_subtensor::Call::sudo { .. } - | pallet_subtensor::Call::sudo_unchecked_weight { .. } - | pallet_subtensor::Call::swap_hotkey { .. } - | pallet_subtensor::Call::vote { .. }) + | RuntimeCall::SubtensorModule( + pallet_subtensor::Call::add_stake { .. } + | pallet_subtensor::Call::become_delegate { .. } + | pallet_subtensor::Call::burned_register { .. } + | pallet_subtensor::Call::commit_weights { .. } + | pallet_subtensor::Call::decrease_take { .. } + | pallet_subtensor::Call::faucet { .. } + | pallet_subtensor::Call::increase_take { .. } + | pallet_subtensor::Call::register { .. } + | pallet_subtensor::Call::register_network { .. } + | pallet_subtensor::Call::remove_stake { .. } + | pallet_subtensor::Call::reveal_weights { .. } + | pallet_subtensor::Call::root_register { .. } + | pallet_subtensor::Call::serve_axon { .. } + | pallet_subtensor::Call::serve_prometheus { .. } + | pallet_subtensor::Call::set_root_weights { .. } + | pallet_subtensor::Call::set_weights { .. } + | pallet_subtensor::Call::sudo { .. } + | pallet_subtensor::Call::sudo_unchecked_weight { .. } + | pallet_subtensor::Call::swap_hotkey { .. } + | pallet_subtensor::Call::vote { .. } + ) ) } } From 7ef1cb509f2fea45d095560c8a8b9bd3c2298b96 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 3 Jul 2024 13:51:22 -0400 Subject: [PATCH 056/185] update cargo.lock --- Cargo.lock | 1079 +++++++++++++++++++++++++++------------------------- 1 file changed, 559 insertions(+), 520 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da4c0fdde..e96685f25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,11 +23,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ - "gimli 0.28.1", + "gimli 0.29.0", ] [[package]] @@ -68,7 +68,7 @@ dependencies = [ "cipher 0.4.4", "ctr", "ghash", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -77,7 +77,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -136,47 +136,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -184,9 +185,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "approx" @@ -204,7 +205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.5", "proc-macro-error", "proc-macro2", "quote", @@ -218,11 +219,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.5", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -311,7 +312,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits", "rayon", "zeroize", @@ -379,7 +380,7 @@ dependencies = [ "ark-std", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -520,9 +521,9 @@ checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" [[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" @@ -588,9 +589,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ "async-lock", "cfg-if", @@ -599,7 +600,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.32", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -607,24 +608,24 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite 0.2.14", ] [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -642,22 +643,22 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ - "addr2line 0.21.0", + "addr2line 0.22.0", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.32.2", + "object 0.36.0", "rustc-demangle", ] @@ -744,13 +745,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.17", + "prettyplease 0.2.20", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -923,9 +924,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -962,9 +963,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" dependencies = [ "serde", ] @@ -986,7 +987,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -994,12 +995,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.91" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd97381a8cc6493395a5afc4c691c1084b3768db713b73aa215217aa245d153" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -1013,9 +1015,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", ] @@ -1068,9 +1070,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1078,7 +1080,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1116,9 +1118,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1127,9 +1129,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -1137,34 +1139,34 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "codespan-reporting" @@ -1178,9 +1180,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "comfy-table" @@ -1189,7 +1191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "strum 0.26.2", - "strum_macros 0.26.2", + "strum_macros 0.26.4", "unicode-width", ] @@ -1217,9 +1219,9 @@ checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1258,7 +1260,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -1415,7 +1417,7 @@ dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "itertools", + "itertools 0.10.5", "log", "smallvec", "wasmparser", @@ -1424,9 +1426,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -1452,9 +1454,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -1470,7 +1472,7 @@ checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -1502,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -1523,24 +1525,23 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version 0.4.0", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -1552,14 +1553,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "cxx" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dc7287237dd438b926a81a1a5605dad33d286870e5eee2db17bf2bcd9e92a" +checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" dependencies = [ "cc", "cxxbridge-flags", @@ -1569,9 +1570,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47c6c8ad7c1a10d3ef0fe3ff6733f4db0d78f08ef0b13121543163ef327058b" +checksum = "d8b2766fbd92be34e9ed143898fce6c572dc009de39506ed6903e5a05b68914e" dependencies = [ "cc", "codespan-reporting", @@ -1579,31 +1580,31 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "cxxbridge-flags" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701a1ac7a697e249cdd8dc026d7a7dafbfd0dbcd8bd24ec55889f2bc13dd6287" +checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" [[package]] name = "cxxbridge-macro" -version = "1.0.120" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b404f596046b0bb2d903a9c786b875a126261b52b7c3a64bbb66382c41c771df" +checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -1611,27 +1612,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 2.0.58", + "strsim", + "syn 2.0.67", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -1641,23 +1642,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1665,9 +1666,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -1737,20 +1738,20 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[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", "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 1.0.109", + "syn 2.0.67", ] [[package]] @@ -1786,7 +1787,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -1833,13 +1834,13 @@ 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", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -1879,9 +1880,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.58", + "syn 2.0.67", "termcolor", - "toml 0.8.13", + "toml 0.8.14", "walkdir", ] @@ -1955,12 +1956,12 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519", "rand_core 0.6.4", "serde", "sha2 0.10.8", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -1980,9 +1981,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -2000,7 +2001,7 @@ dependencies = [ "rand_core 0.6.4", "sec1", "serdect", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -2024,22 +2025,22 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2069,9 +2070,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2112,9 +2113,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -2123,11 +2124,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 4.0.3", + "event-listener 5.3.1", "pin-project-lite 0.2.14", ] @@ -2142,16 +2143,17 @@ dependencies = [ [[package]] name = "expander" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e83c02035136f1592a47964ea60c05a50e4ed8b5892cfac197063850898d4d" +checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2" dependencies = [ "blake2 0.10.6", + "file-guard", "fs-err", - "prettier-please", + "prettyplease 0.2.20", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2168,9 +2170,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdlimit" @@ -2189,7 +2191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -2207,9 +2209,19 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.7" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "file-guard" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "file-per-thread-logger" @@ -2245,7 +2257,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "scale-info", ] @@ -2269,9 +2281,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-sys", @@ -2347,7 +2359,7 @@ version = "32.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "Inflector", - "array-bytes 6.2.2", + "array-bytes 6.2.3", "chrono", "clap", "comfy-table", @@ -2356,7 +2368,7 @@ dependencies = [ "frame-system", "gethostname", "handlebars", - "itertools", + "itertools 0.10.5", "lazy_static", "linked-hash-map", "log", @@ -2426,7 +2438,7 @@ version = "28.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aquamarine 0.5.0", - "array-bytes 6.2.2", + "array-bytes 6.2.3", "bitflags 1.3.2", "docify", "environmental", @@ -2471,13 +2483,13 @@ dependencies = [ "derive-syn-parse 0.2.0", "expander", "frame-support-procedural-tools", - "itertools", + "itertools 0.10.5", "macro_magic", "proc-macro-warning", "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2489,7 +2501,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2499,7 +2511,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2650,7 +2662,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -2752,9 +2764,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -2802,6 +2814,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "glob" version = "0.3.1" @@ -2820,7 +2838,7 @@ dependencies = [ "futures-timer", "no-std-compat", "nonzero_ext", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "portable-atomic", "quanta", "rand", @@ -2836,7 +2854,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -2907,9 +2925,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2921,7 +2939,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -2942,6 +2960,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" @@ -3049,9 +3073,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -3067,9 +3091,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -3082,7 +3106,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.14", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -3099,7 +3123,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-native-certs", "tokio", "tokio-rustls", @@ -3224,18 +3248,18 @@ dependencies = [ [[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", "quote", @@ -3259,7 +3283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3273,9 +3297,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -3295,7 +3319,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -3312,7 +3336,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.6", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -3330,11 +3354,17 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -3344,6 +3374,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -3352,9 +3391,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -3394,7 +3433,7 @@ dependencies = [ "futures-util", "hyper", "jsonrpsee-types", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "rustc-hash", "serde", @@ -3414,7 +3453,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -3499,7 +3538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -3510,7 +3549,7 @@ checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" dependencies = [ "kvdb", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "regex", "rocksdb", "smallvec", @@ -3530,9 +3569,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -3541,7 +3580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3559,7 +3598,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.14", + "getrandom 0.2.15", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -3624,7 +3663,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project", "quick-protobuf", "rand", @@ -3644,7 +3683,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "smallvec", "trust-dns-resolver", ] @@ -3806,7 +3845,7 @@ dependencies = [ "libp2p-identity", "libp2p-tls", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "quinn-proto", "rand", "rustls 0.20.9", @@ -3922,7 +3961,7 @@ dependencies = [ "futures-rustls", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "quicksink", "rw-stream-sink", "soketto", @@ -3995,7 +4034,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -4018,9 +4057,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.16" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" dependencies = [ "cc", "pkg-config", @@ -4068,9 +4107,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -4086,9 +4125,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4129,9 +4168,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "d6eab492fe7f8651add23237ea56dbf11b3c4ff762ab83d40a47f11433421f91" dependencies = [ "libc", "lz4-sys", @@ -4139,9 +4178,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "e9764018d143cc854c9f17f0b907de70f14393b1f502da6375dce70f00514eb3" dependencies = [ "cc", "libc", @@ -4165,7 +4204,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4179,7 +4218,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4190,7 +4229,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4201,7 +4240,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -4246,9 +4285,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memfd" @@ -4256,7 +4295,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.32", + "rustix 0.38.34", ] [[package]] @@ -4315,9 +4354,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -4344,16 +4383,16 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "either", "hashlink", "lioness", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "rand_chacha", "rand_distr", - "subtle 2.4.1", + "subtle 2.6.0", "thiserror", "zeroize", ] @@ -4468,9 +4507,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.5" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea4908d4f23254adda3daa60ffef0f1ac7b8c3e9a864cf3cc154b251908a2ef" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" dependencies = [ "approx", "matrixmultiply", @@ -4749,20 +4788,19 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -4794,20 +4832,19 @@ 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-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -4819,7 +4856,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -4844,6 +4881,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.6.1" @@ -5308,7 +5354,7 @@ dependencies = [ "log", "lz4", "memmap2 0.5.10", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "siphasher", "snap", @@ -5360,7 +5406,7 @@ dependencies = [ "impl-trait-for-tuples", "lru 0.8.1", "parity-util-mem-derive", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "primitive-types", "smallvec", "winapi", @@ -5402,12 +5448,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -5426,15 +5472,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -5451,14 +5497,14 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbkdf2" @@ -5493,9 +5539,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -5504,9 +5550,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -5514,22 +5560,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "pest_meta" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -5538,9 +5584,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.2.6", @@ -5563,7 +5609,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5600,12 +5646,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "polkavm" version = "0.9.3" @@ -5655,7 +5695,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5665,7 +5705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5675,7 +5715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7be503e60cf56c0eb785f90aaba4b583b36bff00e93997d93fef97f9553c39" dependencies = [ "gimli 0.28.1", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "log", "object 0.32.2", "polkavm-common", @@ -5691,15 +5731,15 @@ checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" [[package]] name = "polling" -version = "3.6.0" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite 0.2.14", - "rustix 0.38.32", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -5753,7 +5793,7 @@ checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", - "itertools", + "itertools 0.10.5", "normalize-line-endings", "predicates-core", "regex", @@ -5775,21 +5815,11 @@ dependencies = [ "termtree", ] -[[package]] -name = "prettier-please" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" -dependencies = [ - "proc-macro2", - "syn 2.0.58", -] - [[package]] name = "prettyplease" -version = "0.1.11" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ "proc-macro2", "syn 1.0.109", @@ -5797,12 +5827,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5870,29 +5900,29 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[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.1", + "parking_lot 0.12.3", "thiserror", ] @@ -5904,7 +5934,7 @@ checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" dependencies = [ "dtoa", "itoa", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "prometheus-client-derive-encode", ] @@ -5916,7 +5946,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -5947,12 +5977,12 @@ checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck 0.4.1", - "itertools", + "itertools 0.10.5", "lazy_static", "log", "multimap", "petgraph", - "prettyplease 0.1.11", + "prettyplease 0.1.25", "prost 0.11.9", "prost-types", "regex", @@ -5968,7 +5998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -5981,10 +6011,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -6079,9 +6109,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -6128,7 +6158,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", ] [[package]] @@ -6215,35 +6245,44 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.15", "libredox", "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -6273,14 +6312,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -6294,13 +6333,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -6311,9 +6350,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "resolv-conf" @@ -6332,7 +6371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -6374,7 +6413,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -6445,9 +6484,9 @@ dependencies = [ [[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" @@ -6476,7 +6515,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver 1.0.23", ] [[package]] @@ -6504,14 +6543,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] @@ -6529,9 +6568,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -6572,9 +6611,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rw-stream-sink" @@ -6589,9 +6628,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe-mix" @@ -6604,9 +6643,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", ] @@ -6673,7 +6712,7 @@ name = "sc-chain-spec" version = "27.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "docify", "log", "memmap2 0.9.4", @@ -6702,7 +6741,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -6710,12 +6749,12 @@ name = "sc-cli" version = "0.36.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "chrono", "clap", "fdlimit", "futures", - "itertools", + "itertools 0.10.5", "libp2p-identity", "log", "names", @@ -6755,7 +6794,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -6786,7 +6825,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-state-db", "schnellru", @@ -6810,7 +6849,7 @@ dependencies = [ "libp2p-identity", "log", "mockall", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-utils", "serde", @@ -6859,7 +6898,7 @@ version = "0.19.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ahash 0.8.11", - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-trait", "dyn-clone", "finality-grandpa", @@ -6868,7 +6907,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "sc-block-builder", "sc-chain-spec", @@ -6945,7 +6984,7 @@ version = "0.32.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", @@ -6995,7 +7034,7 @@ dependencies = [ "cfg-if", "libc", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rustix 0.36.17", "sc-allocator", "sc-executor-common", @@ -7026,8 +7065,8 @@ name = "sc-keystore" version = "25.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", - "parking_lot 0.12.1", + "array-bytes 6.2.3", + "parking_lot 0.12.3", "serde_json", "sp-application-crypto", "sp-core", @@ -7051,7 +7090,7 @@ dependencies = [ "mixnet", "multiaddr", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-network", "sc-transaction-pool-api", @@ -7069,7 +7108,7 @@ name = "sc-network" version = "0.34.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-channel", "async-trait", "asynchronous-codec", @@ -7084,7 +7123,7 @@ dependencies = [ "log", "mockall", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "partial_sort", "pin-project", "rand", @@ -7168,7 +7207,7 @@ name = "sc-network-light" version = "0.33.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-channel", "futures", "libp2p-identity", @@ -7189,7 +7228,7 @@ name = "sc-network-sync" version = "0.33.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "async-channel", "async-trait", "fork-tree", @@ -7225,7 +7264,7 @@ name = "sc-network-transactions" version = "0.33.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "futures", "libp2p", "log", @@ -7244,7 +7283,7 @@ name = "sc-offchain" version = "29.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "bytes", "fnv", "futures", @@ -7256,7 +7295,7 @@ dependencies = [ "num_cpus", "once_cell", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "sc-client-api", "sc-network", @@ -7291,7 +7330,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -7357,14 +7396,14 @@ name = "sc-rpc-spec-v2" version = "0.34.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "futures", "futures-util", "hex", "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "sc-chain-spec", "sc-client-api", @@ -7396,7 +7435,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project", "rand", "sc-chain-spec", @@ -7454,7 +7493,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sp-core", ] @@ -7488,7 +7527,7 @@ dependencies = [ "futures", "libp2p", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project", "rand", "sc-utils", @@ -7510,7 +7549,7 @@ dependencies = [ "libc", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "regex", "rustc-hash", "sc-client-api", @@ -7536,7 +7575,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -7550,7 +7589,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sc-client-api", "sc-transaction-pool-api", "sc-utils", @@ -7592,16 +7631,16 @@ dependencies = [ "futures-timer", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "prometheus", "sp-arithmetic", ] [[package]] name = "scale-info" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "bitvec", "cfg-if", @@ -7613,11 +7652,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -7634,9 +7673,9 @@ dependencies = [ [[package]] name = "schnellru" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" dependencies = [ "ahash 0.8.11", "cfg-if", @@ -7652,13 +7691,13 @@ dependencies = [ "aead", "arrayref", "arrayvec", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.8", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -7695,7 +7734,7 @@ dependencies = [ "generic-array 0.14.7", "pkcs8", "serdect", - "subtle 2.4.1", + "subtle 2.6.0", "zeroize", ] @@ -7728,11 +7767,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -7741,9 +7780,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -7769,9 +7808,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -7784,9 +7823,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -7811,13 +7850,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -7865,7 +7904,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -7942,9 +7981,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[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", ] @@ -8020,12 +8059,12 @@ dependencies = [ "aes-gcm", "blake2 0.10.6", "chacha20poly1305", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring 0.17.8", "rustc_version 0.4.0", "sha2 0.10.8", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -8040,9 +8079,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -8098,7 +8137,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8165,7 +8204,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "schnellru", "sp-api", "sp-consensus", @@ -8239,7 +8278,7 @@ name = "sp-core" version = "28.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.2", + "array-bytes 6.2.3", "bandersnatch_vrfs", "bitflags 1.3.2", "blake2 0.10.6", @@ -8251,14 +8290,14 @@ dependencies = [ "hash-db", "hash256-std-hasher", "impl-serde", - "itertools", + "itertools 0.10.5", "k256", "libsecp256k1", "log", "merlin", "parity-bip39", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "paste", "primitive-types", "rand", @@ -8284,7 +8323,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -8321,7 +8360,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8330,7 +8369,7 @@ version = "10.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -8340,17 +8379,17 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8366,7 +8405,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "environmental", "parity-scale-codec", @@ -8438,7 +8477,7 @@ version = "0.34.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "sp-core", "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] @@ -8549,7 +8588,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8575,20 +8614,20 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "Inflector", "expander", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8626,7 +8665,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "smallvec", "sp-core", @@ -8644,7 +8683,7 @@ version = "10.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aes-gcm", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -8670,7 +8709,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" [[package]] name = "sp-storage" @@ -8687,7 +8726,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8722,7 +8761,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "parity-scale-codec", "tracing", @@ -8764,7 +8803,7 @@ dependencies = [ "memory-db", "nohash-hasher", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "scale-info", "schnellru", @@ -8801,7 +8840,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -8819,7 +8858,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ec46106c33f2220d16a9dc7ad604d564d42ee009" +source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" dependencies = [ "impl-trait-for-tuples", "log", @@ -8926,12 +8965,6 @@ dependencies = [ "syn 1.0.109", ] -[[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" @@ -8950,7 +8983,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "strum_macros 0.26.2", + "strum_macros 0.26.4", ] [[package]] @@ -8968,15 +9001,15 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9052,7 +9085,7 @@ dependencies = [ "sp-maybe-compressed-blob", "strum 0.26.2", "tempfile", - "toml 0.8.13", + "toml 0.8.14", "walkdir", "wasm-opt", ] @@ -9089,7 +9122,7 @@ dependencies = [ "ahash 0.8.11", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9100,9 +9133,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" [[package]] name = "syn" @@ -9117,9 +9150,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" dependencies = [ "proc-macro2", "quote", @@ -9179,7 +9212,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.32", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -9198,7 +9231,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.32", + "rustix 0.38.34", "windows-sys 0.48.0", ] @@ -9210,22 +9243,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9265,9 +9298,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -9286,9 +9319,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -9320,32 +9353,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite 0.2.14", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9354,7 +9387,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.12", "tokio", ] @@ -9372,9 +9405,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -9382,7 +9415,6 @@ dependencies = [ "futures-sink", "pin-project-lite 0.2.14", "tokio", - "tracing", ] [[package]] @@ -9396,14 +9428,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.13" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.13", + "toml_edit 0.22.14", ] [[package]] @@ -9428,15 +9460,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.13" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.8", + "winnow 0.6.13", ] [[package]] @@ -9504,7 +9536,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -9659,7 +9691,7 @@ dependencies = [ "ipconfig", "lazy_static", "lru-cache", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "resolv-conf", "smallvec", "thiserror", @@ -9748,9 +9780,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -9765,7 +9797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle 2.6.0", ] [[package]] @@ -9794,9 +9826,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna 0.5.0", @@ -9805,9 +9837,9 @@ dependencies = [ [[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 = "valuable" @@ -9835,9 +9867,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", @@ -9909,7 +9941,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", "wasm-bindgen-shared", ] @@ -9943,7 +9975,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10261,14 +10293,14 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.32", + "rustix 0.38.34", ] [[package]] name = "wide" -version = "0.7.15" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89beec544f246e679fc25490e3f8e08003bc4bf612068f325120dad4cea02c1c" +checksum = "8a040b111774ab63a19ef46bbc149398ab372b4ccdcfd719e9814dbd7dfd76c8" dependencies = [ "bytemuck", "safe_arch", @@ -10298,11 +10330,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -10336,7 +10368,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -10363,7 +10395,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -10398,17 +10430,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -10425,9 +10458,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -10443,9 +10476,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -10461,9 +10494,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -10479,9 +10518,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -10497,9 +10536,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -10515,9 +10554,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -10533,9 +10572,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -10548,9 +10587,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -10591,7 +10630,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -10624,7 +10663,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "rand", "static_assertions", ] @@ -10640,29 +10679,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -10675,7 +10714,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.67", ] [[package]] @@ -10718,9 +10757,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", From dba168e86719e172c2a6a3a379324928d38ff18e Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 3 Jul 2024 13:56:59 -0400 Subject: [PATCH 057/185] fix hash code --- pallets/registry/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index e5063222e..0badd5669 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -279,7 +279,7 @@ impl TypeInfo for IdentityFields { /// /// NOTE: This should be stored at the end of the storage item to facilitate the addition of extra /// fields in a backwards compatible way through a specialized `Decode` impl. -#[freeze_struct("ac2fc4e3c16c9dc")] +#[freeze_struct("98e2d7fc7536226b")] #[derive( CloneNoBound, Encode, Decode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] From 190b254d321f1dea077908963b4d56271190330d Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 3 Jul 2024 13:57:24 -0400 Subject: [PATCH 058/185] fix another hash code --- pallets/subtensor/src/subnet_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/subnet_info.rs b/pallets/subtensor/src/subnet_info.rs index 55c7bcbfa..5bd652ace 100644 --- a/pallets/subtensor/src/subnet_info.rs +++ b/pallets/subtensor/src/subnet_info.rs @@ -4,7 +4,7 @@ use frame_support::storage::IterableStorageMap; extern crate alloc; use codec::Compact; -#[freeze_struct("4d3e8df520bbc960")] +#[freeze_struct("fe79d58173da662a")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct SubnetInfo { netuid: Compact, From 0ab4a91b092ea5f91e371165273a72e7c62bd06d Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 3 Jul 2024 14:00:09 -0400 Subject: [PATCH 059/185] update another hash code --- pallets/subtensor/src/subnet_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/subnet_info.rs b/pallets/subtensor/src/subnet_info.rs index 5bd652ace..4e9e756a0 100644 --- a/pallets/subtensor/src/subnet_info.rs +++ b/pallets/subtensor/src/subnet_info.rs @@ -27,7 +27,7 @@ pub struct SubnetInfo { owner: T::AccountId, } -#[freeze_struct("76f4053b3cc4c7ec")] +#[freeze_struct("55b472510f10e76a")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct SubnetHyperparams { rho: Compact, From ac74347d616f0407c0146cef0d8940379bf83a80 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 3 Jul 2024 22:11:47 +0400 Subject: [PATCH 060/185] feat: cold key swap --- pallets/subtensor/src/errors.rs | 10 + pallets/subtensor/src/events.rs | 7 + pallets/subtensor/src/swap.rs | 285 +++++++++++++++++++++++ pallets/subtensor/tests/swap.rs | 394 ++++++++++++++++++++++++++++++++ scripts/test_specific.sh | 4 +- 5 files changed, 699 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index 57ba91d31..9dd01fda4 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -132,5 +132,15 @@ mod errors { AlphaHighTooLow, /// Alpha low is out of range: alpha_low > 0 && alpha_low < 0.8 AlphaLowOutOfRange, + /// The coldkey has already been swapped + ColdKeyAlreadyAssociated, + /// The coldkey swap transaction rate limit exceeded + ColdKeySwapTxRateLimitExceeded, + /// The new coldkey is the same as the old coldkey + NewColdKeyIsSameWithOld, + /// The coldkey does not exist + NotExistColdkey, + /// The coldkey balance is not enough to pay for the swap + NotEnoughBalanceToPaySwapColdKey, } } diff --git a/pallets/subtensor/src/events.rs b/pallets/subtensor/src/events.rs index 47cc9973b..73bda500b 100644 --- a/pallets/subtensor/src/events.rs +++ b/pallets/subtensor/src/events.rs @@ -132,5 +132,12 @@ mod events { MinDelegateTakeSet(u16), /// the target stakes per interval is set by sudo/admin transaction TargetStakesPerIntervalSet(u64), + /// A coldkey has been swapped + ColdkeySwapped { + /// the account ID of old coldkey + old_coldkey: T::AccountId, + /// the account ID of new coldkey + new_coldkey: T::AccountId, + }, } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index ce090d736..fd934fa08 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -92,6 +92,102 @@ impl Pallet { Ok(Some(weight).into()) } + /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which must be signed by the old coldkey. + /// * `old_coldkey` - The account ID of the old coldkey. + /// * `new_coldkey` - The account ID of the new coldkey. + /// + /// # Returns + /// + /// Returns a `DispatchResultWithPostInfo` indicating success or failure, along with the weight consumed. + /// + /// # Errors + /// + /// This function will return an error if: + /// - The caller is not the old coldkey. + /// - The new coldkey is the same as the old coldkey. + /// - The new coldkey is already associated with other hotkeys. + /// - The transaction rate limit for coldkey swaps has been exceeded. + /// - There's not enough balance to pay for the swap. + /// + /// # Events + /// + /// Emits a `ColdkeySwapped` event when successful. + /// + /// # Weight + /// + /// Weight is tracked and updated throughout the function execution. + pub fn do_swap_coldkey( + origin: T::RuntimeOrigin, + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + + // Ensure the caller is the old coldkey + ensure!(caller == *old_coldkey, Error::::NonAssociatedColdKey); + + let mut weight = T::DbWeight::get().reads(2); + + ensure!( + old_coldkey != new_coldkey, + Error::::NewColdKeyIsSameWithOld + ); + + // Check if the new coldkey is already associated with any hotkeys + ensure!( + !Self::coldkey_has_associated_hotkeys(new_coldkey), + Error::::ColdKeyAlreadyAssociated + ); + + let block: u64 = Self::get_current_block_as_u64(); + ensure!( + !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(old_coldkey), block), + Error::::ColdKeySwapTxRateLimitExceeded + ); + + // Note: we probably want to make this free + let swap_cost = Self::get_coldkey_swap_cost(); + ensure!( + Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + let actual_burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + + // Swap coldkey references in storage maps + Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); + Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); + Self::swap_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); + Self::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( + old_coldkey, + new_coldkey, + &mut weight, + ); + Self::swap_keys_for_coldkey(old_coldkey, new_coldkey, &mut weight); + Self::swap_subnet_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); + + // Transfer any remaining balance from old_coldkey to new_coldkey + let remaining_balance = Self::get_coldkey_balance(old_coldkey); + if remaining_balance > 0 { + Self::remove_balance_from_coldkey_account(old_coldkey, remaining_balance)?; + Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); + } + + Self::set_last_tx_block(new_coldkey, block); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + Self::deposit_event(Event::ColdkeySwapped { + old_coldkey: old_coldkey.clone(), + new_coldkey: new_coldkey.clone(), + }); + + Ok(Some(weight).into()) + } + /// Retrieves the network membership status for a given hotkey. /// /// # Arguments @@ -396,4 +492,193 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove } } + + /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Removes the total stake from the old coldkey. + /// * Inserts the total stake for the new coldkey. + /// * Updates the transaction weight. + pub fn swap_total_coldkey_stake( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + let stake = TotalColdkeyStake::::get(old_coldkey); + TotalColdkeyStake::::remove(old_coldkey); + TotalColdkeyStake::::insert(new_coldkey, stake); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + /// Swaps all stakes associated with a coldkey from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Removes all stakes associated with the old coldkey. + /// * Inserts all stakes for the new coldkey. + /// * Updates the transaction weight. + pub fn swap_stake_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + for (hotkey, stake) in Stake::::iter_prefix(old_coldkey) { + Stake::::remove(old_coldkey, &hotkey); + Stake::::insert(new_coldkey, &hotkey, stake); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + /// Swaps the owner of all hotkeys from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Updates the owner of all hotkeys associated with the old coldkey to the new coldkey. + /// * Updates the transaction weight. + pub fn swap_owner_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + for (hotkey, _) in Owner::::iter() { + if Owner::::get(&hotkey) == *old_coldkey { + Owner::::insert(&hotkey, new_coldkey); + } + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Removes all total hotkey-coldkey stakes for the current interval associated with the old coldkey. + /// * Inserts all total hotkey-coldkey stakes for the current interval for the new coldkey. + /// * Updates the transaction weight. + pub fn swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + for (hotkey, (stakes, block)) in + TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_coldkey) + { + TotalHotkeyColdkeyStakesThisInterval::::remove(old_coldkey, &hotkey); + TotalHotkeyColdkeyStakesThisInterval::::insert( + new_coldkey, + &hotkey, + (stakes, block), + ); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + /// Swaps all keys associated with a coldkey from the old coldkey to the new coldkey across all networks. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Updates all keys associated with the old coldkey to be associated with the new coldkey. + /// * Updates the transaction weight. + pub fn swap_keys_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + for netuid in 0..TotalNetworks::::get() { + for (uid, hotkey) in Keys::::iter_prefix(netuid) { + if Owner::::get(&hotkey) == *old_coldkey { + Keys::::remove(netuid, uid); + Keys::::insert(netuid, uid, new_coldkey); + } + } + } + weight.saturating_accrue(T::DbWeight::get().reads_writes( + TotalNetworks::::get() as u64, + TotalNetworks::::get() as u64, + )); + } + + /// Checks if a coldkey has any associated hotkeys. + /// + /// # Arguments + /// + /// * `coldkey` - The AccountId of the coldkey to check. + /// + /// # Returns + /// + /// * `bool` - True if the coldkey has any associated hotkeys, false otherwise. + pub fn coldkey_has_associated_hotkeys(coldkey: &T::AccountId) -> bool { + Owner::::iter().any(|(_, owner)| owner == *coldkey) + } + + /// Swaps the subnet owner from the old coldkey to the new coldkey for all networks where the old coldkey is the owner. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Updates the subnet owner to the new coldkey for all networks where the old coldkey was the owner. + /// * Updates the transaction weight. + pub fn swap_subnet_owner_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + for netuid in 0..TotalNetworks::::get() { + let subnet_owner = SubnetOwner::::get(netuid); + if subnet_owner == *old_coldkey { + SubnetOwner::::insert(netuid, new_coldkey.clone()); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + } + weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); + } + + /// Returns the cost of swapping a coldkey. + /// + /// # Returns + /// + /// * `u64` - The cost of swapping a coldkey in Rao. + /// + /// # Note + /// + /// This function returns a hardcoded value. In a production environment, this should be configurable or determined dynamically. + pub fn get_coldkey_swap_cost() -> u64 { + 1_000_000 // Example cost in Rao + } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index af7d19d2d..47f4fa401 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1048,3 +1048,397 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_weight_update() { assert_eq!(weight, expected_weight); }); } + +#[test] +fn test_do_swap_coldkey_success() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let netuid = 1u16; + let stake_amount = 1000u64; + let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, old_coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); + + // Add stake to the neuron + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + stake_amount + )); + + log::info!("TotalColdkeyStake::::get(old_coldkey): {:?}", TotalColdkeyStake::::get(old_coldkey)); + log::info!("Stake::::get(old_coldkey, hotkey): {:?}", Stake::::get(old_coldkey, hotkey)); + + // Verify initial stake + assert_eq!(TotalColdkeyStake::::get(old_coldkey), stake_amount); + // assert_eq!(Stake::::get(old_coldkey, hotkey), stake_amount); + + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, + &new_coldkey + )); + + // Verify the swap + assert_eq!(Owner::::get(hotkey), new_coldkey); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake_amount); + assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); + assert_eq!(Stake::::get(new_coldkey, hotkey), stake_amount); + assert!(!Stake::::contains_key(old_coldkey, hotkey)); + + // Verify balance transfer + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), stake_amount); + assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); + + + + // Verify event emission + System::assert_last_event(Event::ColdkeySwapped { + old_coldkey: old_coldkey, + new_coldkey: new_coldkey, + }.into()); + }); +} + +#[test] +fn test_do_swap_coldkey_not_enough_balance() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + + // Setup initial state with insufficient balance + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost - 1); + + // Attempt the swap + assert_err!( + SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, + &new_coldkey + ), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + }); +} + +#[test] +fn test_do_swap_coldkey_same_keys() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + + // Attempt the swap with same old and new coldkeys + assert_err!( + SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(coldkey), + &coldkey, + &coldkey + ), + Error::::NewColdKeyIsSameWithOld + ); + }); +} + +#[test] +fn test_do_swap_coldkey_new_key_already_associated() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let netuid = 1u16; + let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey1, old_coldkey, 0); + register_ok_neuron(netuid, hotkey2, new_coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost); + + // Attempt the swap + assert_err!( + SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, + &new_coldkey + ), + Error::::ColdKeyAlreadyAssociated + ); + }); +} + +#[test] +fn test_swap_total_coldkey_stake() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let stake_amount = 1000u64; + let mut weight = Weight::zero(); + + // Initialize TotalColdkeyStake for old_coldkey + TotalColdkeyStake::::insert(old_coldkey, stake_amount); + + // Perform the swap + SubtensorModule::swap_total_coldkey_stake(&old_coldkey, &new_coldkey, &mut weight); + + // Verify the swap + assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake_amount); + assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(1, 2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_stake_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let mut weight = Weight::zero(); + + // Initialize Stake for old_coldkey + Stake::::insert(old_coldkey, hotkey1, stake_amount1); + Stake::::insert(old_coldkey, hotkey2, stake_amount2); + + // Perform the swap + SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify the swap + assert_eq!(Stake::::get(new_coldkey, hotkey1), stake_amount1); + assert_eq!(Stake::::get(new_coldkey, hotkey2), stake_amount2); + assert!(!Stake::::contains_key(old_coldkey, hotkey1)); + assert!(!Stake::::contains_key(old_coldkey, hotkey2)); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(1, 4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_owner_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let mut weight = Weight::zero(); + + // Initialize Owner for old_coldkey + Owner::::insert(hotkey1, old_coldkey); + Owner::::insert(hotkey2, old_coldkey); + + // Perform the swap + SubtensorModule::swap_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify the swap + assert_eq!(Owner::::get(hotkey1), new_coldkey); + assert_eq!(Owner::::get(hotkey2), new_coldkey); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(1, 2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake1 = (1000u64, 100u64); + let stake2 = (2000u64, 200u64); + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyColdkeyStakesThisInterval for old_coldkey + TotalHotkeyColdkeyStakesThisInterval::::insert(old_coldkey, hotkey1, stake1); + TotalHotkeyColdkeyStakesThisInterval::::insert(old_coldkey, hotkey2, stake2); + + // Perform the swap + SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight, + ); + + // Verify the swap + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(new_coldkey, hotkey1), + stake1 + ); + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(new_coldkey, hotkey2), + stake2 + ); + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( + old_coldkey, + hotkey1 + )); + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( + old_coldkey, + hotkey2 + )); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(1, 4); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_keys_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let netuid1 = 1u16; + let netuid2 = 2u16; + let uid1 = 10u16; + let uid2 = 20u16; + let mut weight = Weight::zero(); + + // Initialize Keys and Owner for old_coldkey + Keys::::insert(netuid1, uid1, hotkey1); + Keys::::insert(netuid2, uid2, hotkey2); + Owner::::insert(hotkey1, old_coldkey); + Owner::::insert(hotkey2, old_coldkey); + + // Set up TotalNetworks + TotalNetworks::::put(3); + + // Perform the swap + SubtensorModule::swap_keys_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify the swap + assert_eq!(Keys::::get(netuid1, uid1), hotkey1); + assert_eq!(Keys::::get(netuid2, uid2), hotkey2); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(3, 2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_swap_subnet_owner_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let netuid1 = 1u16; + let netuid2 = 2u16; + let mut weight = Weight::zero(); + + // Initialize SubnetOwner for old_coldkey + SubnetOwner::::insert(netuid1, old_coldkey); + SubnetOwner::::insert(netuid2, old_coldkey); + + // Set up TotalNetworks + TotalNetworks::::put(3); + + // Perform the swap + SubtensorModule::swap_subnet_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify the swap + assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); + assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(3, 2); + assert_eq!(weight, expected_weight); + }); +} + +#[test] +fn test_do_swap_coldkey_with_subnet_ownership() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let netuid = 1u16; + let stake_amount = 1000u64; + let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, old_coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); + SubnetOwner::::insert(netuid, old_coldkey); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, + &new_coldkey + )); + + // Verify subnet ownership transfer + assert_eq!(SubnetOwner::::get(netuid), new_coldkey); + }); +} + +#[test] +fn test_do_swap_coldkey_tx_rate_limit() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + + // Setup initial state + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost * 2); + + // Perform first swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, + &new_coldkey + )); + + // Attempt second swap immediately + assert_err!( + SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(new_coldkey), + &new_coldkey, + &old_coldkey + ), + Error::::ColdKeySwapTxRateLimitExceeded + ); + }); +} + +#[test] +fn test_coldkey_has_associated_hotkeys() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = 1u16; + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Check if coldkey has associated hotkeys + assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); + + // Check for a coldkey without associated hotkeys + let unassociated_coldkey = U256::from(3); + assert!(!SubtensorModule::coldkey_has_associated_hotkeys( + &unassociated_coldkey + )); + }); +} diff --git a/scripts/test_specific.sh b/scripts/test_specific.sh index 4e413c6d1..018872d33 100755 --- a/scripts/test_specific.sh +++ b/scripts/test_specific.sh @@ -1,4 +1,6 @@ pallet="${3:-pallet-subtensor}" features="${4:-pow-faucet}" -RUST_LOG="pallet_subtensor=trace,info" cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file +# RUST_LOG="pallet_subtensor=info" cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact + +RUST_LOG=INFO cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file From 86e9d7b729f363ce5004b3fa881cdca775df945b Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 3 Jul 2024 16:16:15 -0400 Subject: [PATCH 061/185] Add Owned map and migration to populate it --- pallets/subtensor/src/lib.rs | 28 +++++++++++++++++++- pallets/subtensor/src/migration.rs | 41 ++++++++++++++++++++++++++++++ pallets/subtensor/src/staking.rs | 8 ++++++ pallets/subtensor/src/swap.rs | 36 +++++++++++++++++++------- pallets/subtensor/tests/swap.rs | 17 ++++++++----- 5 files changed, 113 insertions(+), 17 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 64aefcfee..243079528 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -363,6 +363,9 @@ pub mod pallet { #[pallet::storage] // --- MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; + #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. + pub type Owned = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] // --- MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. pub type Delegates = StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; @@ -1204,6 +1207,14 @@ pub mod pallet { // Fill stake information. Owner::::insert(hotkey.clone(), coldkey.clone()); + // Update Owned map + let mut hotkeys = Owned::::get(&coldkey); + hotkeys.push(hotkey.clone()); + Owned::::insert( + &coldkey, + hotkeys, + ); + TotalHotkeyStake::::insert(hotkey.clone(), stake); TotalColdkeyStake::::insert( coldkey.clone(), @@ -1325,7 +1336,9 @@ pub mod pallet { // Storage version v4 -> v5 .saturating_add(migration::migrate_delete_subnet_3::()) // Doesn't check storage version. TODO: Remove after upgrade - .saturating_add(migration::migration5_total_issuance::(false)); + .saturating_add(migration::migration5_total_issuance::(false)) + // Populate Owned map for coldkey swap. Doesn't update storage vesion. + .saturating_add(migration::migrate_populate_owned::()); weight } @@ -1970,6 +1983,19 @@ pub mod pallet { Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) } + /// The extrinsic for user to change the coldkey + #[pallet::call_index(71)] + #[pallet::weight((Weight::from_parts(1_940_000_000, 0) + .saturating_add(T::DbWeight::get().reads(272)) + .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + pub fn swap_coldkey( + origin: OriginFor, + old_coldkey: T::AccountId, + new_coldkey: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::do_swap_coldkey(origin, &old_coldkey, &new_coldkey) + } + // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ // ================================== diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 86fc02054..f5a5ee7db 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -477,3 +477,44 @@ pub fn migrate_to_v2_fixed_total_stake() -> Weight { Weight::zero() } } + +pub fn migrate_populate_owned() -> Weight { + // Setup migration weight + let mut weight = T::DbWeight::get().reads(1); + let migration_name = "Populate Owned map"; + + // Check if this migration is needed (if Owned map is empty) + let migrate = Owned::::iter().next().is_none(); + + // Only runs if we haven't already updated version past above new_storage_version. + if migrate { + info!(target: LOG_TARGET_1, ">>> Migration: {}", migration_name); + + let mut longest_hotkey_vector = 0; + let mut longest_coldkey: Option = None; + Owner::::iter().for_each(|(hotkey, coldkey)| { + let mut hotkeys = Owned::::get(&coldkey); + hotkeys.push(hotkey); + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } + + Owned::::insert( + &coldkey, + hotkeys, + ); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + }); + info!(target: LOG_TARGET_1, "Migration {} finished. Longest hotkey vector: {}", migration_name, longest_hotkey_vector); + if let Some(c) = longest_coldkey { + info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {}", c); + } + + weight + } else { + info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); + Weight::zero() + } +} diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 7c3328396..0cce2dd35 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -560,6 +560,14 @@ impl Pallet { if !Self::hotkey_account_exists(hotkey) { Stake::::insert(hotkey, coldkey, 0); Owner::::insert(hotkey, coldkey); + + // Update Owned map + let mut hotkeys = Owned::::get(&coldkey); + hotkeys.push(hotkey.clone()); + Owned::::insert( + &coldkey, + hotkeys, + ); } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index fd934fa08..d01be5ee4 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -223,6 +223,15 @@ impl Pallet { ) { Owner::::remove(old_hotkey); Owner::::insert(new_hotkey, coldkey.clone()); + + // Update Owned map + let mut hotkeys = Owned::::get(&coldkey); + hotkeys.push(new_hotkey.clone()); + Owned::::insert( + &coldkey, + hotkeys, + ); + weight.saturating_accrue(T::DbWeight::get().writes(2)); } @@ -535,11 +544,15 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - for (hotkey, stake) in Stake::::iter_prefix(old_coldkey) { - Stake::::remove(old_coldkey, &hotkey); - Stake::::insert(new_coldkey, &hotkey, stake); + // Find all hotkeys for this coldkey + let hotkeys = Owned::::get(old_coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + for hotkey in hotkeys.iter() { + let stake = Stake::::get(&hotkey, old_coldkey); + Stake::::remove(&hotkey, old_coldkey); + Stake::::insert(&hotkey, new_coldkey, stake); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } /// Swaps the owner of all hotkeys from the old coldkey to the new coldkey. @@ -559,12 +572,17 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - for (hotkey, _) in Owner::::iter() { - if Owner::::get(&hotkey) == *old_coldkey { - Owner::::insert(&hotkey, new_coldkey); - } + let hotkeys = Owned::::get(old_coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + for hotkey in hotkeys.iter() { + Owner::::insert(&hotkey, new_coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); } - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // Update Owned map with new coldkey + Owned::::remove(old_coldkey); + Owned::::insert(new_coldkey, hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); } /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 47f4fa401..5d3f5e6a8 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1210,20 +1210,23 @@ fn test_swap_stake_for_coldkey() { let mut weight = Weight::zero(); // Initialize Stake for old_coldkey - Stake::::insert(old_coldkey, hotkey1, stake_amount1); - Stake::::insert(old_coldkey, hotkey2, stake_amount2); + Stake::::insert(hotkey1, old_coldkey, stake_amount1); + Stake::::insert(hotkey2, old_coldkey, stake_amount2); + + // Populate Owned map + Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); // Verify the swap - assert_eq!(Stake::::get(new_coldkey, hotkey1), stake_amount1); - assert_eq!(Stake::::get(new_coldkey, hotkey2), stake_amount2); - assert!(!Stake::::contains_key(old_coldkey, hotkey1)); - assert!(!Stake::::contains_key(old_coldkey, hotkey2)); + assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); + assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); + assert!(!Stake::::contains_key(hotkey1, old_coldkey)); + assert!(!Stake::::contains_key(hotkey2, old_coldkey)); // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 4); + let expected_weight = ::DbWeight::get().reads_writes(3, 4); assert_eq!(weight, expected_weight); }); } From 347b8de6dd9543a8a3cec0c154a18d1472e5ce2d Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 3 Jul 2024 16:23:30 -0400 Subject: [PATCH 062/185] Swapping coldkeys: unbreak the build --- pallets/subtensor/src/migration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index f5a5ee7db..a24d0d03b 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -509,7 +509,7 @@ pub fn migrate_populate_owned() -> Weight { }); info!(target: LOG_TARGET_1, "Migration {} finished. Longest hotkey vector: {}", migration_name, longest_hotkey_vector); if let Some(c) = longest_coldkey { - info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {}", c); + info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); } weight From bda0c5a31b63d0916b32022cc14629d1442e8f60 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 3 Jul 2024 19:16:40 -0400 Subject: [PATCH 063/185] Fix coldkey_swap and its tests --- pallets/subtensor/src/lib.rs | 12 ++-- pallets/subtensor/src/migration.rs | 10 ++-- pallets/subtensor/src/staking.rs | 37 ++++++++++-- pallets/subtensor/src/swap.rs | 85 +++++++++++++--------------- pallets/subtensor/tests/swap.rs | 91 ++++++++++++++---------------- 5 files changed, 125 insertions(+), 110 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 243079528..2fde2f293 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1209,11 +1209,13 @@ pub mod pallet { // Update Owned map let mut hotkeys = Owned::::get(&coldkey); - hotkeys.push(hotkey.clone()); - Owned::::insert( - &coldkey, - hotkeys, - ); + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey.clone()); + Owned::::insert( + &coldkey, + hotkeys, + ); + } TotalHotkeyStake::::insert(hotkey.clone(), stake); TotalColdkeyStake::::insert( diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index a24d0d03b..0873ef034 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -494,10 +494,12 @@ pub fn migrate_populate_owned() -> Weight { let mut longest_coldkey: Option = None; Owner::::iter().for_each(|(hotkey, coldkey)| { let mut hotkeys = Owned::::get(&coldkey); - hotkeys.push(hotkey); - if longest_hotkey_vector < hotkeys.len() { - longest_hotkey_vector = hotkeys.len(); - longest_coldkey = Some(coldkey.clone()); + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } } Owned::::insert( diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 0cce2dd35..913a7208b 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -563,11 +563,13 @@ impl Pallet { // Update Owned map let mut hotkeys = Owned::::get(&coldkey); - hotkeys.push(hotkey.clone()); - Owned::::insert( - &coldkey, - hotkeys, - ); + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey.clone()); + Owned::::insert( + &coldkey, + hotkeys, + ); + } } } @@ -789,6 +791,31 @@ impl Pallet { Ok(credit) } + pub fn kill_coldkey_account( + coldkey: &T::AccountId, + amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, + ) -> Result { + if amount == 0 { + return Ok(0); + } + + let credit = T::Currency::withdraw( + coldkey, + amount, + Precision::Exact, + Preservation::Expendable, + Fortitude::Force, + ) + .map_err(|_| Error::::BalanceWithdrawalError)? + .peek(); + + if credit == 0 { + return Err(Error::::ZeroBalanceAfterWithdrawn.into()); + } + + Ok(credit) + } + pub fn unstake_all_coldkeys_from_hotkey_account(hotkey: &T::AccountId) { // Iterate through all coldkeys that have a stake on this hotkey account. for (delegate_coldkey_i, stake_i) in diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index d01be5ee4..dd06bc1fe 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -167,13 +167,13 @@ impl Pallet { new_coldkey, &mut weight, ); - Self::swap_keys_for_coldkey(old_coldkey, new_coldkey, &mut weight); Self::swap_subnet_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); + Self::swap_owned_for_coldkey(old_coldkey, new_coldkey, &mut weight); // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { - Self::remove_balance_from_coldkey_account(old_coldkey, remaining_balance)?; + Self::kill_coldkey_account(old_coldkey, remaining_balance)?; Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); } @@ -226,7 +226,10 @@ impl Pallet { // Update Owned map let mut hotkeys = Owned::::get(&coldkey); - hotkeys.push(new_hotkey.clone()); + if !hotkeys.contains(&new_hotkey) { + hotkeys.push(new_hotkey.clone()); + } + hotkeys.retain(|hk| *hk != *old_hotkey); Owned::::insert( &coldkey, hotkeys, @@ -578,11 +581,6 @@ impl Pallet { Owner::::insert(&hotkey, new_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); } - - // Update Owned map with new coldkey - Owned::::remove(old_coldkey); - Owned::::insert(new_coldkey, hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); } /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. @@ -603,48 +601,17 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - for (hotkey, (stakes, block)) in - TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_coldkey) - { - TotalHotkeyColdkeyStakesThisInterval::::remove(old_coldkey, &hotkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + for hotkey in Owned::::get(old_coldkey).iter() { + let (stake, block) = TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, old_coldkey); + TotalHotkeyColdkeyStakesThisInterval::::remove(&hotkey, old_coldkey); TotalHotkeyColdkeyStakesThisInterval::::insert( - new_coldkey, &hotkey, - (stakes, block), + new_coldkey, + (stake, block), ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } - - /// Swaps all keys associated with a coldkey from the old coldkey to the new coldkey across all networks. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Updates all keys associated with the old coldkey to be associated with the new coldkey. - /// * Updates the transaction weight. - pub fn swap_keys_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - for netuid in 0..TotalNetworks::::get() { - for (uid, hotkey) in Keys::::iter_prefix(netuid) { - if Owner::::get(&hotkey) == *old_coldkey { - Keys::::remove(netuid, uid); - Keys::::insert(netuid, uid, new_coldkey); - } - } - } - weight.saturating_accrue(T::DbWeight::get().reads_writes( - TotalNetworks::::get() as u64, - TotalNetworks::::get() as u64, - )); } /// Checks if a coldkey has any associated hotkeys. @@ -677,7 +644,7 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - for netuid in 0..TotalNetworks::::get() { + for netuid in 0..=TotalNetworks::::get() { let subnet_owner = SubnetOwner::::get(netuid); if subnet_owner == *old_coldkey { SubnetOwner::::insert(netuid, new_coldkey.clone()); @@ -687,6 +654,30 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } + /// Swaps the owned hotkeys for the coldkey + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Updates the subnet owner to the new coldkey for all networks where the old coldkey was the owner. + /// * Updates the transaction weight. + pub fn swap_owned_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + // Update Owned map with new coldkey + let hotkeys = Owned::::get(&old_coldkey); + Owned::::remove(old_coldkey); + Owned::::insert(new_coldkey, hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); + } + /// Returns the cost of swapping a coldkey. /// /// # Returns diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 5d3f5e6a8..db9c55823 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1058,11 +1058,12 @@ fn test_do_swap_coldkey_success() { let netuid = 1u16; let stake_amount = 1000u64; let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + let free_balance = 12345; // Setup initial state add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost + free_balance); // Add stake to the neuron assert_ok!(SubtensorModule::add_stake( @@ -1072,12 +1073,22 @@ fn test_do_swap_coldkey_success() { )); log::info!("TotalColdkeyStake::::get(old_coldkey): {:?}", TotalColdkeyStake::::get(old_coldkey)); - log::info!("Stake::::get(old_coldkey, hotkey): {:?}", Stake::::get(old_coldkey, hotkey)); + log::info!("Stake::::get(old_coldkey, hotkey): {:?}", Stake::::get(hotkey, old_coldkey)); // Verify initial stake assert_eq!(TotalColdkeyStake::::get(old_coldkey), stake_amount); - // assert_eq!(Stake::::get(old_coldkey, hotkey), stake_amount); + assert_eq!(Stake::::get(hotkey, old_coldkey), stake_amount); + + + assert_eq!(Owned::::get(old_coldkey), vec![hotkey]); + assert!(!Owned::::contains_key(new_coldkey)); + + + + // Get coldkey free balance before swap + let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); + assert_eq!(balance, free_balance + swap_cost); // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( @@ -1086,19 +1097,19 @@ fn test_do_swap_coldkey_success() { &new_coldkey )); - // Verify the swap + // Verify the swap assert_eq!(Owner::::get(hotkey), new_coldkey); assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake_amount); assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); - assert_eq!(Stake::::get(new_coldkey, hotkey), stake_amount); - assert!(!Stake::::contains_key(old_coldkey, hotkey)); + assert_eq!(Stake::::get(hotkey, new_coldkey), stake_amount); + assert!(!Stake::::contains_key(hotkey, old_coldkey)); + assert_eq!(Owned::::get(new_coldkey), vec![hotkey]); + assert!(!Owned::::contains_key(old_coldkey)); // Verify balance transfer - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), stake_amount); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance - swap_cost); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); - - // Verify event emission System::assert_last_event(Event::ColdkeySwapped { old_coldkey: old_coldkey, @@ -1244,6 +1255,9 @@ fn test_swap_owner_for_coldkey() { Owner::::insert(hotkey1, old_coldkey); Owner::::insert(hotkey2, old_coldkey); + // Initialize Owned map + Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); + // Perform the swap SubtensorModule::swap_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); @@ -1269,8 +1283,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { let mut weight = Weight::zero(); // Initialize TotalHotkeyColdkeyStakesThisInterval for old_coldkey - TotalHotkeyColdkeyStakesThisInterval::::insert(old_coldkey, hotkey1, stake1); - TotalHotkeyColdkeyStakesThisInterval::::insert(old_coldkey, hotkey2, stake2); + TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey1, old_coldkey, stake1); + TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey2, old_coldkey, stake2); + + // Populate Owned map + Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( @@ -1281,11 +1298,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { // Verify the swap assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(new_coldkey, hotkey1), + TotalHotkeyColdkeyStakesThisInterval::::get(hotkey1, new_coldkey), stake1 ); assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(new_coldkey, hotkey2), + TotalHotkeyColdkeyStakesThisInterval::::get(hotkey2, new_coldkey), stake2 ); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( @@ -1298,42 +1315,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { )); // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 4); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_keys_for_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - let netuid1 = 1u16; - let netuid2 = 2u16; - let uid1 = 10u16; - let uid2 = 20u16; - let mut weight = Weight::zero(); - - // Initialize Keys and Owner for old_coldkey - Keys::::insert(netuid1, uid1, hotkey1); - Keys::::insert(netuid2, uid2, hotkey2); - Owner::::insert(hotkey1, old_coldkey); - Owner::::insert(hotkey2, old_coldkey); - - // Set up TotalNetworks - TotalNetworks::::put(3); - - // Perform the swap - SubtensorModule::swap_keys_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // Verify the swap - assert_eq!(Keys::::get(netuid1, uid1), hotkey1); - assert_eq!(Keys::::get(netuid2, uid2), hotkey2); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(3, 2); + let expected_weight = ::DbWeight::get().reads_writes(5, 4); assert_eq!(weight, expected_weight); }); } @@ -1380,9 +1362,16 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Setup initial state add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 0); + + // Set TotalNetworks because swap relies on it + pallet_subtensor::TotalNetworks::::set(1); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); SubnetOwner::::insert(netuid, old_coldkey); + // Populate Owned map + Owned::::insert(old_coldkey, vec![hotkey]); + // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), @@ -1402,8 +1391,12 @@ fn test_do_swap_coldkey_tx_rate_limit() { let new_coldkey = U256::from(2); let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + // Set non-zero tx rate limit + SubtensorModule::set_tx_rate_limit(1); + // Setup initial state SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost * 2); + SubtensorModule::add_balance_to_coldkey_account(&new_coldkey, swap_cost * 2); // Perform first swap assert_ok!(SubtensorModule::do_swap_coldkey( From 49d19b1f86bbf6dd7ab7dc75544cdedea33d4d6c Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Thu, 4 Jul 2024 14:39:57 +0400 Subject: [PATCH 064/185] chore: lints --- justfile | 6 +---- pallets/subtensor/src/lib.rs | 13 ++++------- pallets/subtensor/src/migration.rs | 5 +--- pallets/subtensor/src/staking.rs | 9 +++----- pallets/subtensor/src/swap.rs | 24 ++++++++----------- pallets/subtensor/tests/swap.rs | 37 +++++++++++++++++++----------- 6 files changed, 43 insertions(+), 51 deletions(-) diff --git a/justfile b/justfile index a75b0052b..871daa6f9 100644 --- a/justfile +++ b/justfile @@ -35,11 +35,7 @@ clippy-fix: -A clippy::todo \ -A clippy::unimplemented \ -A clippy::indexing_slicing - @echo "Running cargo clippy with automatic fixes on potentially dirty code..." - cargo +{{RUSTV}} clippy --fix --allow-dirty --workspace --all-targets -- \ - -A clippy::todo \ - -A clippy::unimplemented \ - -A clippy::indexing_slicing + fix: @echo "Running cargo fix..." cargo +{{RUSTV}} fix --workspace diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 2fde2f293..395115935 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1208,15 +1208,12 @@ pub mod pallet { Owner::::insert(hotkey.clone(), coldkey.clone()); // Update Owned map - let mut hotkeys = Owned::::get(&coldkey); - if !hotkeys.contains(&hotkey) { + let mut hotkeys = Owned::::get(coldkey); + if !hotkeys.contains(hotkey) { hotkeys.push(hotkey.clone()); - Owned::::insert( - &coldkey, - hotkeys, - ); + Owned::::insert(coldkey, hotkeys); } - + TotalHotkeyStake::::insert(hotkey.clone(), stake); TotalColdkeyStake::::insert( coldkey.clone(), @@ -1994,7 +1991,7 @@ pub mod pallet { origin: OriginFor, old_coldkey: T::AccountId, new_coldkey: T::AccountId, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResultWithPostInfo { Self::do_swap_coldkey(origin, &old_coldkey, &new_coldkey) } diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 0873ef034..8263e347b 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -502,10 +502,7 @@ pub fn migrate_populate_owned() -> Weight { } } - Owned::::insert( - &coldkey, - hotkeys, - ); + Owned::::insert(&coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); }); diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 913a7208b..eac47ba21 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -562,13 +562,10 @@ impl Pallet { Owner::::insert(hotkey, coldkey); // Update Owned map - let mut hotkeys = Owned::::get(&coldkey); - if !hotkeys.contains(&hotkey) { + let mut hotkeys = Owned::::get(coldkey); + if !hotkeys.contains(hotkey) { hotkeys.push(hotkey.clone()); - Owned::::insert( - &coldkey, - hotkeys, - ); + Owned::::insert(coldkey, hotkeys); } } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index dd06bc1fe..d56f60897 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -225,15 +225,12 @@ impl Pallet { Owner::::insert(new_hotkey, coldkey.clone()); // Update Owned map - let mut hotkeys = Owned::::get(&coldkey); - if !hotkeys.contains(&new_hotkey) { + let mut hotkeys = Owned::::get(coldkey); + if !hotkeys.contains(new_hotkey) { hotkeys.push(new_hotkey.clone()); } hotkeys.retain(|hk| *hk != *old_hotkey); - Owned::::insert( - &coldkey, - hotkeys, - ); + Owned::::insert(coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().writes(2)); } @@ -547,7 +544,7 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - // Find all hotkeys for this coldkey + // Find all hotkeys for this coldkey let hotkeys = Owned::::get(old_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); for hotkey in hotkeys.iter() { @@ -603,13 +600,10 @@ impl Pallet { ) { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); for hotkey in Owned::::get(old_coldkey).iter() { - let (stake, block) = TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, old_coldkey); + let (stake, block) = + TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, old_coldkey); TotalHotkeyColdkeyStakesThisInterval::::remove(&hotkey, old_coldkey); - TotalHotkeyColdkeyStakesThisInterval::::insert( - &hotkey, - new_coldkey, - (stake, block), - ); + TotalHotkeyColdkeyStakesThisInterval::::insert(&hotkey, new_coldkey, (stake, block)); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } } @@ -672,12 +666,12 @@ impl Pallet { weight: &mut Weight, ) { // Update Owned map with new coldkey - let hotkeys = Owned::::get(&old_coldkey); + let hotkeys = Owned::::get(old_coldkey); Owned::::remove(old_coldkey); Owned::::insert(new_coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); } - + /// Returns the cost of swapping a coldkey. /// /// # Returns diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index db9c55823..273a21bcf 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1063,7 +1063,10 @@ fn test_do_swap_coldkey_success() { // Setup initial state add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost + free_balance); + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount + swap_cost + free_balance, + ); // Add stake to the neuron assert_ok!(SubtensorModule::add_stake( @@ -1072,20 +1075,22 @@ fn test_do_swap_coldkey_success() { stake_amount )); - log::info!("TotalColdkeyStake::::get(old_coldkey): {:?}", TotalColdkeyStake::::get(old_coldkey)); - log::info!("Stake::::get(old_coldkey, hotkey): {:?}", Stake::::get(hotkey, old_coldkey)); + log::info!( + "TotalColdkeyStake::::get(old_coldkey): {:?}", + TotalColdkeyStake::::get(old_coldkey) + ); + log::info!( + "Stake::::get(old_coldkey, hotkey): {:?}", + Stake::::get(hotkey, old_coldkey) + ); // Verify initial stake assert_eq!(TotalColdkeyStake::::get(old_coldkey), stake_amount); assert_eq!(Stake::::get(hotkey, old_coldkey), stake_amount); - assert_eq!(Owned::::get(old_coldkey), vec![hotkey]); assert!(!Owned::::contains_key(new_coldkey)); - - - // Get coldkey free balance before swap let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); assert_eq!(balance, free_balance + swap_cost); @@ -1107,14 +1112,20 @@ fn test_do_swap_coldkey_success() { assert!(!Owned::::contains_key(old_coldkey)); // Verify balance transfer - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance - swap_cost); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey), + balance - swap_cost + ); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); // Verify event emission - System::assert_last_event(Event::ColdkeySwapped { - old_coldkey: old_coldkey, - new_coldkey: new_coldkey, - }.into()); + System::assert_last_event( + Event::ColdkeySwapped { + old_coldkey, + new_coldkey, + } + .into(), + ); }); } @@ -1255,7 +1266,7 @@ fn test_swap_owner_for_coldkey() { Owner::::insert(hotkey1, old_coldkey); Owner::::insert(hotkey2, old_coldkey); - // Initialize Owned map + // Initialize Owned map Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap From c82f82feb7900dde4e7eb61075066e1151c5d4c5 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Thu, 4 Jul 2024 15:44:14 +0400 Subject: [PATCH 065/185] chore: pr comments --- pallets/subtensor/src/lib.rs | 10 ++-- pallets/subtensor/src/migration.rs | 10 ++-- pallets/subtensor/src/staking.rs | 6 +-- pallets/subtensor/src/swap.rs | 38 ++++++------- pallets/subtensor/tests/swap.rs | 85 +++++------------------------- 5 files changed, 44 insertions(+), 105 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 395115935..ba768f950 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -364,7 +364,7 @@ pub mod pallet { pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. - pub type Owned = + pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] // --- MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. pub type Delegates = @@ -1207,11 +1207,11 @@ pub mod pallet { // Fill stake information. Owner::::insert(hotkey.clone(), coldkey.clone()); - // Update Owned map - let mut hotkeys = Owned::::get(coldkey); + // Update OwnedHotkeys map + let mut hotkeys = OwnedHotkeys::::get(coldkey); if !hotkeys.contains(hotkey) { hotkeys.push(hotkey.clone()); - Owned::::insert(coldkey, hotkeys); + OwnedHotkeys::::insert(coldkey, hotkeys); } TotalHotkeyStake::::insert(hotkey.clone(), stake); @@ -1336,7 +1336,7 @@ pub mod pallet { .saturating_add(migration::migrate_delete_subnet_3::()) // Doesn't check storage version. TODO: Remove after upgrade .saturating_add(migration::migration5_total_issuance::(false)) - // Populate Owned map for coldkey swap. Doesn't update storage vesion. + // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. .saturating_add(migration::migrate_populate_owned::()); weight diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 8263e347b..cc77ac978 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -481,10 +481,10 @@ pub fn migrate_to_v2_fixed_total_stake() -> Weight { pub fn migrate_populate_owned() -> Weight { // Setup migration weight let mut weight = T::DbWeight::get().reads(1); - let migration_name = "Populate Owned map"; + let migration_name = "Populate OwnedHotkeys map"; - // Check if this migration is needed (if Owned map is empty) - let migrate = Owned::::iter().next().is_none(); + // Check if this migration is needed (if OwnedHotkeys map is empty) + let migrate = OwnedHotkeys::::iter().next().is_none(); // Only runs if we haven't already updated version past above new_storage_version. if migrate { @@ -493,7 +493,7 @@ pub fn migrate_populate_owned() -> Weight { let mut longest_hotkey_vector = 0; let mut longest_coldkey: Option = None; Owner::::iter().for_each(|(hotkey, coldkey)| { - let mut hotkeys = Owned::::get(&coldkey); + let mut hotkeys = OwnedHotkeys::::get(&coldkey); if !hotkeys.contains(&hotkey) { hotkeys.push(hotkey); if longest_hotkey_vector < hotkeys.len() { @@ -502,7 +502,7 @@ pub fn migrate_populate_owned() -> Weight { } } - Owned::::insert(&coldkey, hotkeys); + OwnedHotkeys::::insert(&coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); }); diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index eac47ba21..de2a54e4f 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -561,11 +561,11 @@ impl Pallet { Stake::::insert(hotkey, coldkey, 0); Owner::::insert(hotkey, coldkey); - // Update Owned map - let mut hotkeys = Owned::::get(coldkey); + // Update OwnedHotkeys map + let mut hotkeys = OwnedHotkeys::::get(coldkey); if !hotkeys.contains(hotkey) { hotkeys.push(hotkey.clone()); - Owned::::insert(coldkey, hotkeys); + OwnedHotkeys::::insert(coldkey, hotkeys); } } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index d56f60897..2971fbfe2 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -137,17 +137,17 @@ impl Pallet { Error::::NewColdKeyIsSameWithOld ); - // Check if the new coldkey is already associated with any hotkeys - ensure!( - !Self::coldkey_has_associated_hotkeys(new_coldkey), - Error::::ColdKeyAlreadyAssociated - ); + // // Check if the new coldkey is already associated with any hotkeys + // ensure!( + // !Self::coldkey_has_associated_hotkeys(new_coldkey), + // Error::::ColdKeyAlreadyAssociated + // ); let block: u64 = Self::get_current_block_as_u64(); - ensure!( - !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(old_coldkey), block), - Error::::ColdKeySwapTxRateLimitExceeded - ); + // ensure!( + // !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(old_coldkey), block), + // Error::::ColdKeySwapTxRateLimitExceeded + // ); // Note: we probably want to make this free let swap_cost = Self::get_coldkey_swap_cost(); @@ -224,13 +224,13 @@ impl Pallet { Owner::::remove(old_hotkey); Owner::::insert(new_hotkey, coldkey.clone()); - // Update Owned map - let mut hotkeys = Owned::::get(coldkey); + // Update OwnedHotkeys map + let mut hotkeys = OwnedHotkeys::::get(coldkey); if !hotkeys.contains(new_hotkey) { hotkeys.push(new_hotkey.clone()); } hotkeys.retain(|hk| *hk != *old_hotkey); - Owned::::insert(coldkey, hotkeys); + OwnedHotkeys::::insert(coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().writes(2)); } @@ -545,7 +545,7 @@ impl Pallet { weight: &mut Weight, ) { // Find all hotkeys for this coldkey - let hotkeys = Owned::::get(old_coldkey); + let hotkeys = OwnedHotkeys::::get(old_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); for hotkey in hotkeys.iter() { let stake = Stake::::get(&hotkey, old_coldkey); @@ -572,7 +572,7 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - let hotkeys = Owned::::get(old_coldkey); + let hotkeys = OwnedHotkeys::::get(old_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); for hotkey in hotkeys.iter() { Owner::::insert(&hotkey, new_coldkey); @@ -599,7 +599,7 @@ impl Pallet { weight: &mut Weight, ) { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); - for hotkey in Owned::::get(old_coldkey).iter() { + for hotkey in OwnedHotkeys::::get(old_coldkey).iter() { let (stake, block) = TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, old_coldkey); TotalHotkeyColdkeyStakesThisInterval::::remove(&hotkey, old_coldkey); @@ -665,10 +665,10 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - // Update Owned map with new coldkey - let hotkeys = Owned::::get(old_coldkey); - Owned::::remove(old_coldkey); - Owned::::insert(new_coldkey, hotkeys); + // Update OwnedHotkeys map with new coldkey + let hotkeys = OwnedHotkeys::::get(old_coldkey); + OwnedHotkeys::::remove(old_coldkey); + OwnedHotkeys::::insert(new_coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 273a21bcf..ebdb5aed7 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1088,8 +1088,8 @@ fn test_do_swap_coldkey_success() { assert_eq!(TotalColdkeyStake::::get(old_coldkey), stake_amount); assert_eq!(Stake::::get(hotkey, old_coldkey), stake_amount); - assert_eq!(Owned::::get(old_coldkey), vec![hotkey]); - assert!(!Owned::::contains_key(new_coldkey)); + assert_eq!(OwnedHotkeys::::get(old_coldkey), vec![hotkey]); + assert!(!OwnedHotkeys::::contains_key(new_coldkey)); // Get coldkey free balance before swap let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); @@ -1108,8 +1108,8 @@ fn test_do_swap_coldkey_success() { assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); assert_eq!(Stake::::get(hotkey, new_coldkey), stake_amount); assert!(!Stake::::contains_key(hotkey, old_coldkey)); - assert_eq!(Owned::::get(new_coldkey), vec![hotkey]); - assert!(!Owned::::contains_key(old_coldkey)); + assert_eq!(OwnedHotkeys::::get(new_coldkey), vec![hotkey]); + assert!(!OwnedHotkeys::::contains_key(old_coldkey)); // Verify balance transfer assert_eq!( @@ -1168,34 +1168,6 @@ fn test_do_swap_coldkey_same_keys() { }); } -#[test] -fn test_do_swap_coldkey_new_key_already_associated() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - let netuid = 1u16; - let swap_cost = SubtensorModule::get_coldkey_swap_cost(); - - // Setup initial state - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey1, old_coldkey, 0); - register_ok_neuron(netuid, hotkey2, new_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost); - - // Attempt the swap - assert_err!( - SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, - &new_coldkey - ), - Error::::ColdKeyAlreadyAssociated - ); - }); -} - #[test] fn test_swap_total_coldkey_stake() { new_test_ext(1).execute_with(|| { @@ -1235,8 +1207,8 @@ fn test_swap_stake_for_coldkey() { Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); - // Populate Owned map - Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); + // Populate OwnedHotkeys map + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); @@ -1266,8 +1238,8 @@ fn test_swap_owner_for_coldkey() { Owner::::insert(hotkey1, old_coldkey); Owner::::insert(hotkey2, old_coldkey); - // Initialize Owned map - Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); + // Initialize OwnedHotkeys map + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap SubtensorModule::swap_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); @@ -1297,8 +1269,8 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey1, old_coldkey, stake1); TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey2, old_coldkey, stake2); - // Populate Owned map - Owned::::insert(old_coldkey, vec![hotkey1, hotkey2]); + // Populate OwnedHotkeys map + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( @@ -1380,8 +1352,8 @@ fn test_do_swap_coldkey_with_subnet_ownership() { SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); SubnetOwner::::insert(netuid, old_coldkey); - // Populate Owned map - Owned::::insert(old_coldkey, vec![hotkey]); + // Populate OwnedHotkeys map + OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( @@ -1395,39 +1367,6 @@ fn test_do_swap_coldkey_with_subnet_ownership() { }); } -#[test] -fn test_do_swap_coldkey_tx_rate_limit() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let swap_cost = SubtensorModule::get_coldkey_swap_cost(); - - // Set non-zero tx rate limit - SubtensorModule::set_tx_rate_limit(1); - - // Setup initial state - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost * 2); - SubtensorModule::add_balance_to_coldkey_account(&new_coldkey, swap_cost * 2); - - // Perform first swap - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, - &new_coldkey - )); - - // Attempt second swap immediately - assert_err!( - SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(new_coldkey), - &new_coldkey, - &old_coldkey - ), - Error::::ColdKeySwapTxRateLimitExceeded - ); - }); -} - #[test] fn test_coldkey_has_associated_hotkeys() { new_test_ext(1).execute_with(|| { From 1f753d8d266625fd4820446155d4dbf242062cb4 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 4 Jul 2024 10:25:48 -0400 Subject: [PATCH 066/185] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9a43fdf93..c091fb462 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 154, + spec_version: 155, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From ae4cdf31e1e27f895d791c5cef932d5c878d2947 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 4 Jul 2024 10:26:11 -0400 Subject: [PATCH 067/185] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9a43fdf93..c091fb462 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 154, + spec_version: 155, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 0855df737704acd9868f7d3688021d643657c89a Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Thu, 4 Jul 2024 19:06:06 +0400 Subject: [PATCH 068/185] chore: pr review, make swaps free --- pallets/subtensor/src/migration.rs | 36 +++++++++++++++---- pallets/subtensor/src/swap.rs | 52 +++------------------------ pallets/subtensor/tests/swap.rs | 57 +++--------------------------- runtime/src/lib.rs | 2 +- 4 files changed, 39 insertions(+), 108 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index cc77ac978..5dbdd5f42 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -478,6 +478,7 @@ pub fn migrate_to_v2_fixed_total_stake() -> Weight { } } +/// Migrate the OwnedHotkeys map to the new storage format pub fn migrate_populate_owned() -> Weight { // Setup migration weight let mut weight = T::DbWeight::get().reads(1); @@ -486,27 +487,48 @@ pub fn migrate_populate_owned() -> Weight { // Check if this migration is needed (if OwnedHotkeys map is empty) let migrate = OwnedHotkeys::::iter().next().is_none(); - // Only runs if we haven't already updated version past above new_storage_version. + // Only runs if the migration is needed if migrate { - info!(target: LOG_TARGET_1, ">>> Migration: {}", migration_name); + info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); - let mut longest_hotkey_vector = 0; + let mut longest_hotkey_vector: usize = 0; let mut longest_coldkey: Option = None; + let mut keys_touched: u64 = 0; + let mut storage_reads: u64 = 0; + let mut storage_writes: u64 = 0; + + // Iterate through all Owner entries Owner::::iter().for_each(|(hotkey, coldkey)| { + storage_reads = storage_reads.saturating_add(1); // Read from Owner storage let mut hotkeys = OwnedHotkeys::::get(&coldkey); + storage_reads = storage_reads.saturating_add(1); // Read from OwnedHotkeys storage + + // Add the hotkey if it's not already in the vector if !hotkeys.contains(&hotkey) { hotkeys.push(hotkey); + keys_touched = keys_touched.saturating_add(1); + + // Update longest hotkey vector info if longest_hotkey_vector < hotkeys.len() { longest_hotkey_vector = hotkeys.len(); longest_coldkey = Some(coldkey.clone()); } - } - OwnedHotkeys::::insert(&coldkey, hotkeys); + // Update the OwnedHotkeys storage + OwnedHotkeys::::insert(&coldkey, hotkeys); + storage_writes = storage_writes.saturating_add(1); // Write to OwnedHotkeys storage + } - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + // Accrue weight for reads and writes + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); }); - info!(target: LOG_TARGET_1, "Migration {} finished. Longest hotkey vector: {}", migration_name, longest_hotkey_vector); + + // Log migration results + info!( + target: LOG_TARGET_1, + "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", + migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes + ); if let Some(c) = longest_coldkey { info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 2971fbfe2..b6aed8b72 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -53,16 +53,6 @@ impl Pallet { T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); - let swap_cost = Self::get_hotkey_swap_cost(); - log::debug!("Swap cost: {:?}", swap_cost); - - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), - Error::::NotEnoughBalanceToPaySwapHotKey - ); - let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - Self::burn_tokens(actual_burn_amount); - Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); @@ -125,38 +115,17 @@ impl Pallet { old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { - let caller = ensure_signed(origin)?; - - // Ensure the caller is the old coldkey - ensure!(caller == *old_coldkey, Error::::NonAssociatedColdKey); + ensure_signed(origin)?; let mut weight = T::DbWeight::get().reads(2); + // Check if the new coldkey is already associated with any hotkeys ensure!( - old_coldkey != new_coldkey, - Error::::NewColdKeyIsSameWithOld + !Self::coldkey_has_associated_hotkeys(new_coldkey), + Error::::ColdKeyAlreadyAssociated ); - // // Check if the new coldkey is already associated with any hotkeys - // ensure!( - // !Self::coldkey_has_associated_hotkeys(new_coldkey), - // Error::::ColdKeyAlreadyAssociated - // ); - let block: u64 = Self::get_current_block_as_u64(); - // ensure!( - // !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(old_coldkey), block), - // Error::::ColdKeySwapTxRateLimitExceeded - // ); - - // Note: we probably want to make this free - let swap_cost = Self::get_coldkey_swap_cost(); - ensure!( - Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - let actual_burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost)?; - Self::burn_tokens(actual_burn_amount); // Swap coldkey references in storage maps Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); @@ -671,17 +640,4 @@ impl Pallet { OwnedHotkeys::::insert(new_coldkey, hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); } - - /// Returns the cost of swapping a coldkey. - /// - /// # Returns - /// - /// * `u64` - The cost of swapping a coldkey in Rao. - /// - /// # Note - /// - /// This function returns a hardcoded value. In a production environment, this should be configurable or determined dynamically. - pub fn get_coldkey_swap_cost() -> u64 { - 1_000_000 // Example cost in Rao - } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index ebdb5aed7..65c9f2d4b 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1057,16 +1057,12 @@ fn test_do_swap_coldkey_success() { let hotkey = U256::from(3); let netuid = 1u16; let stake_amount = 1000u64; - let swap_cost = SubtensorModule::get_coldkey_swap_cost(); let free_balance = 12345; // Setup initial state add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount + swap_cost + free_balance, - ); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + free_balance); // Add stake to the neuron assert_ok!(SubtensorModule::add_stake( @@ -1093,7 +1089,7 @@ fn test_do_swap_coldkey_success() { // Get coldkey free balance before swap let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); - assert_eq!(balance, free_balance + swap_cost); + assert_eq!(balance, free_balance); // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( @@ -1112,10 +1108,7 @@ fn test_do_swap_coldkey_success() { assert!(!OwnedHotkeys::::contains_key(old_coldkey)); // Verify balance transfer - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey), - balance - swap_cost - ); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); // Verify event emission @@ -1129,45 +1122,6 @@ fn test_do_swap_coldkey_success() { }); } -#[test] -fn test_do_swap_coldkey_not_enough_balance() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let swap_cost = SubtensorModule::get_coldkey_swap_cost(); - - // Setup initial state with insufficient balance - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost - 1); - - // Attempt the swap - assert_err!( - SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, - &new_coldkey - ), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - }); -} - -#[test] -fn test_do_swap_coldkey_same_keys() { - new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1); - - // Attempt the swap with same old and new coldkeys - assert_err!( - SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(coldkey), - &coldkey, - &coldkey - ), - Error::::NewColdKeyIsSameWithOld - ); - }); -} - #[test] fn test_swap_total_coldkey_stake() { new_test_ext(1).execute_with(|| { @@ -1339,8 +1293,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { let new_coldkey = U256::from(2); let hotkey = U256::from(3); let netuid = 1u16; - let stake_amount = 1000u64; - let swap_cost = SubtensorModule::get_coldkey_swap_cost(); + let stake_amount: u64 = 1000u64; // Setup initial state add_network(netuid, 13, 0); @@ -1349,7 +1302,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Set TotalNetworks because swap relies on it pallet_subtensor::TotalNetworks::::set(1); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount); SubnetOwner::::insert(netuid, old_coldkey); // Populate OwnedHotkeys map diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9a43fdf93..fe9ea0bd8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 154, + spec_version: 156, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 621725f462a149b94dbcc63b1ba6f628287de4df Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Thu, 4 Jul 2024 21:36:37 +0400 Subject: [PATCH 069/185] chore: unstake + transfer --- justfile | 4 +- pallets/subtensor/src/errors.rs | 4 + pallets/subtensor/src/events.rs | 15 ++ pallets/subtensor/src/lib.rs | 44 ++++- pallets/subtensor/src/staking.rs | 94 ++++++++++ pallets/subtensor/tests/staking.rs | 290 +++++++++++++++++++++++++++++ 6 files changed, 448 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 871daa6f9..e33fdf685 100644 --- a/justfile +++ b/justfile @@ -31,11 +31,11 @@ clippy: clippy-fix: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." - cargo +{{RUSTV}} clippy --fix --allow-dirty --workspace --all-targets -- \ + cargo +{{RUSTV}} clippy --fix --allow-dirty --allow-staged --workspace --all-targets -- \ -A clippy::todo \ -A clippy::unimplemented \ -A clippy::indexing_slicing - + fix: @echo "Running cargo fix..." cargo +{{RUSTV}} fix --workspace diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index 9dd01fda4..3e30c094c 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -142,5 +142,9 @@ mod errors { NotExistColdkey, /// The coldkey balance is not enough to pay for the swap NotEnoughBalanceToPaySwapColdKey, + /// No balance to transfer + NoBalanceToTransfer, + /// Same coldkey + SameColdkey, } } diff --git a/pallets/subtensor/src/events.rs b/pallets/subtensor/src/events.rs index 73bda500b..167f10170 100644 --- a/pallets/subtensor/src/events.rs +++ b/pallets/subtensor/src/events.rs @@ -139,5 +139,20 @@ mod events { /// the account ID of new coldkey new_coldkey: T::AccountId, }, + /// All balance of a hotkey has been unstaked and transferred to a new coldkey + AllBalanceUnstakedAndTransferredToNewColdkey { + /// The account ID of the current coldkey + current_coldkey: T::AccountId, + /// The account ID of the new coldkey + new_coldkey: T::AccountId, + /// The account ID of the hotkey + hotkey: T::AccountId, + /// The current stake of the hotkey + current_stake: u64, + /// The total balance of the hotkey + total_balance: <::Currency as fungible::Inspect< + ::AccountId, + >>::Balance, + }, } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ba768f950..873168d65 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1982,7 +1982,21 @@ pub mod pallet { Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) } - /// The extrinsic for user to change the coldkey + /// The extrinsic for user to change the coldkey associated with their account. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, must be signed by the old coldkey. + /// * `old_coldkey` - The current coldkey associated with the account. + /// * `new_coldkey` - The new coldkey to be associated with the account. + /// + /// # Returns + /// + /// Returns a `DispatchResultWithPostInfo` indicating success or failure of the operation. + /// + /// # Weight + /// + /// Weight is calculated based on the number of database reads and writes. #[pallet::call_index(71)] #[pallet::weight((Weight::from_parts(1_940_000_000, 0) .saturating_add(T::DbWeight::get().reads(272)) @@ -1995,6 +2009,34 @@ pub mod pallet { Self::do_swap_coldkey(origin, &old_coldkey, &new_coldkey) } + /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, must be signed by the current coldkey. + /// * `hotkey` - The hotkey associated with the stakes to be unstaked. + /// * `new_coldkey` - The new coldkey to receive the unstaked tokens. + /// + /// # Returns + /// + /// Returns a `DispatchResult` indicating success or failure of the operation. + /// + /// # Weight + /// + /// Weight is calculated based on the number of database reads and writes. + #[pallet::call_index(72)] + #[pallet::weight((Weight::from_parts(1_940_000_000, 0) + .saturating_add(T::DbWeight::get().reads(272)) + .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + pub fn unstake_all_and_transfer_to_new_coldkey( + origin: OriginFor, + hotkey: T::AccountId, + new_coldkey: T::AccountId, + ) -> DispatchResult { + let current_coldkey = ensure_signed(origin)?; + Self::do_unstake_all_and_transfer_to_new_coldkey(current_coldkey, hotkey, new_coldkey) + } + // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ // ================================== diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index de2a54e4f..6c87f1131 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -1,4 +1,5 @@ use super::*; +use dispatch::RawOrigin; use frame_support::{ storage::IterableStorageDoubleMap, traits::{ @@ -9,6 +10,7 @@ use frame_support::{ Imbalance, }, }; +use num_traits::Zero; impl Pallet { /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. @@ -827,4 +829,96 @@ impl Pallet { Self::add_balance_to_coldkey_account(&delegate_coldkey_i, stake_i); } } + + /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. + /// + /// This function performs the following operations: + /// 1. Verifies that the hotkey exists and is owned by the current coldkey. + /// 2. Ensures that the new coldkey is different from the current one. + /// 3. Unstakes all balance if there's any stake. + /// 4. Transfers the entire balance of the hotkey to the new coldkey. + /// 5. Verifies the success of the transfer and handles partial transfers if necessary. + /// + /// # Arguments + /// + /// * `current_coldkey` - The AccountId of the current coldkey. + /// * `hotkey` - The AccountId of the hotkey whose balance is being unstaked and transferred. + /// * `new_coldkey` - The AccountId of the new coldkey to receive the unstaked tokens. + /// + /// # Returns + /// + /// Returns a `DispatchResult` indicating success or failure of the operation. + /// + /// # Errors + /// + /// This function will return an error if: + /// * The hotkey account does not exist. + /// * The current coldkey does not own the hotkey. + /// * The new coldkey is the same as the current coldkey. + /// * There is no balance to transfer. + /// * The transfer fails or is only partially successful. + /// + /// # Events + /// + /// Emits an `AllBalanceUnstakedAndTransferredToNewColdkey` event upon successful execution. + /// Emits a `PartialBalanceTransferredToNewColdkey` event if only a partial transfer is successful. + /// + pub fn do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey: T::AccountId, + hotkey: T::AccountId, + new_coldkey: T::AccountId, + ) -> DispatchResult { + // Ensure the hotkey exists and is owned by the current coldkey + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + ensure!( + Self::coldkey_owns_hotkey(¤t_coldkey, &hotkey), + Error::::NonAssociatedColdKey + ); + + // Ensure the new coldkey is different from the current one + ensure!(current_coldkey != new_coldkey, Error::::SameColdkey); + + // Get the current stake + let current_stake: u64 = Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &hotkey); + + // Unstake all balance if there's any stake + if current_stake > 0 { + Self::do_remove_stake( + RawOrigin::Signed(current_coldkey.clone()).into(), + hotkey.clone(), + current_stake, + )?; + } + + // Get the total balance of the current coldkey account + // let total_balance: <::Currency as fungible::Inspect<::AccountId>>::Balance = T::Currency::total_balance(¤t_coldkey); + + let total_balance = Self::get_coldkey_balance(¤t_coldkey); + log::info!("Total Bank Balance: {:?}", total_balance); + + // Ensure there's a balance to transfer + ensure!(!total_balance.is_zero(), Error::::NoBalanceToTransfer); + + // Attempt to transfer the entire total balance to the new coldkey + T::Currency::transfer( + ¤t_coldkey, + &new_coldkey, + total_balance, + Preservation::Expendable, + )?; + + // Emit the event + Self::deposit_event(Event::AllBalanceUnstakedAndTransferredToNewColdkey { + current_coldkey: current_coldkey.clone(), + new_coldkey: new_coldkey.clone(), + hotkey: hotkey.clone(), + current_stake, + total_balance, + }); + + Ok(()) + } } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 529332f04..d4441c7c9 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3127,3 +3127,293 @@ fn test_rate_limits_enforced_on_increase_take() { assert_eq!(SubtensorModule::get_hotkey_take(&hotkey0), u16::MAX / 8); }); } + +// Helper function to set up a test environment +fn setup_test_environment() -> (AccountId, AccountId, AccountId) { + let current_coldkey = U256::from(1); + let hotkey = U256::from(2); + let new_coldkey = U256::from(3); + // Register the neuron to a new network + let netuid = 1; + add_network(netuid, 0, 0); + + // Register the hotkey and associate it with the current coldkey + register_ok_neuron(1, hotkey, current_coldkey, 0); + + // Add some balance to the hotkey + SubtensorModule::add_balance_to_coldkey_account(¤t_coldkey, 1000); + + // Stake some amount + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(current_coldkey), + hotkey, + 500 + )); + + (current_coldkey, hotkey, new_coldkey) +} + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { + new_test_ext(1).execute_with(|| { + let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); + + assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + hotkey, + new_coldkey + )); + + // Check that the stake has been removed + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 0); + + // Check that the balance has been transferred to the new coldkey + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); + + // Check that the appropriate event was emitted + System::assert_last_event( + Event::AllBalanceUnstakedAndTransferredToNewColdkey { + current_coldkey, + new_coldkey, + hotkey, + current_stake: 500, + total_balance: 1000, + } + .into(), + ); + }); +} + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_hotkey_not_exists() { + new_test_ext(1).execute_with(|| { + let current_coldkey = U256::from(1); + let hotkey = U256::from(2); + let new_coldkey = U256::from(3); + + assert_err!( + SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + hotkey, + new_coldkey + ), + Error::::HotKeyAccountNotExists + ); + }); +} + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_non_associated_coldkey() { + new_test_ext(1).execute_with(|| { + let (_, hotkey, new_coldkey) = setup_test_environment(); + let wrong_coldkey = U256::from(4); + + assert_noop!( + SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + wrong_coldkey, + hotkey, + new_coldkey + ), + Error::::NonAssociatedColdKey + ); + }); +} + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { + new_test_ext(1).execute_with(|| { + let (current_coldkey, hotkey, _) = setup_test_environment(); + + assert_noop!( + SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + hotkey, + current_coldkey + ), + Error::::SameColdkey + ); + }); +} + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { + new_test_ext(1).execute_with(|| { + // Create accounts manually + let current_coldkey: AccountId = U256::from(1); + let hotkey: AccountId = U256::from(2); + let new_coldkey: AccountId = U256::from(3); + + add_network(1, 0, 0); + + // Register the hotkey and associate it with the current coldkey + register_ok_neuron(1, hotkey, current_coldkey, 0); + + // Print initial balances + log::info!( + "Initial current_coldkey balance: {:?}", + Balances::total_balance(¤t_coldkey) + ); + log::info!( + "Initial hotkey balance: {:?}", + Balances::total_balance(&hotkey) + ); + log::info!( + "Initial new_coldkey balance: {:?}", + Balances::total_balance(&new_coldkey) + ); + + // Ensure there's no balance in any of the accounts + assert_eq!(Balances::total_balance(¤t_coldkey), 0); + assert_eq!(Balances::total_balance(&hotkey), 0); + assert_eq!(Balances::total_balance(&new_coldkey), 0); + + // Try to unstake and transfer + let result = SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + hotkey, + new_coldkey, + ); + + // Print the result + log::info!( + "Result of do_unstake_all_and_transfer_to_new_coldkey: {:?}", + result + ); + + // Print final balances + log::info!( + "Final current_coldkey balance: {:?}", + Balances::total_balance(¤t_coldkey) + ); + log::info!( + "Final hotkey balance: {:?}", + Balances::total_balance(&hotkey) + ); + log::info!( + "Final new_coldkey balance: {:?}", + Balances::total_balance(&new_coldkey) + ); + + // Assert the expected error + assert_noop!(result, Error::::NoBalanceToTransfer); + + // Verify that no balance was transferred + assert_eq!(Balances::total_balance(¤t_coldkey), 0); + assert_eq!(Balances::total_balance(&hotkey), 0); + assert_eq!(Balances::total_balance(&new_coldkey), 0); + }); +} + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { + new_test_ext(1).execute_with(|| { + // Create accounts manually + let current_coldkey: AccountId = U256::from(1); + let hotkey: AccountId = U256::from(2); + let new_coldkey: AccountId = U256::from(3); + + add_network(1, 0, 0); + + // Register the hotkey and associate it with the current coldkey + register_ok_neuron(1, hotkey, current_coldkey, 0); + + // Add balance to the current coldkey without staking + let initial_balance = 500; + Balances::make_free_balance_be(¤t_coldkey, initial_balance); + + // Print initial balances + log::info!( + "Initial current_coldkey balance: {:?}", + Balances::total_balance(¤t_coldkey) + ); + log::info!( + "Initial hotkey balance: {:?}", + Balances::total_balance(&hotkey) + ); + log::info!( + "Initial new_coldkey balance: {:?}", + Balances::total_balance(&new_coldkey) + ); + + // Ensure initial balances are correct + assert_eq!(Balances::total_balance(¤t_coldkey), initial_balance); + assert_eq!(Balances::total_balance(&hotkey), 0); + assert_eq!(Balances::total_balance(&new_coldkey), 0); + + // Perform unstake and transfer + assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + hotkey, + new_coldkey + )); + + // Print final balances + log::info!( + "Final current_coldkey balance: {:?}", + Balances::total_balance(¤t_coldkey) + ); + log::info!( + "Final hotkey balance: {:?}", + Balances::total_balance(&hotkey) + ); + log::info!( + "Final new_coldkey balance: {:?}", + Balances::total_balance(&new_coldkey) + ); + + // Check that the balance has been transferred to the new coldkey + assert_eq!(Balances::total_balance(&new_coldkey), initial_balance); + assert_eq!(Balances::total_balance(¤t_coldkey), 0); + + // Check that the appropriate event was emitted + System::assert_last_event( + Event::AllBalanceUnstakedAndTransferredToNewColdkey { + current_coldkey, + new_coldkey, + hotkey, + current_stake: 0, + total_balance: initial_balance, + } + .into(), + ); + }); +} +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { + new_test_ext(1).execute_with(|| { + let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); + + SubtensorModule::set_target_stakes_per_interval(10); + + // Add more stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(current_coldkey), + hotkey, + 300 + )); + + assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + hotkey, + new_coldkey + )); + + // Check that all stake has been removed + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 0); + + // Check that the full balance has been transferred to the new coldkey + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); + + // Check that the appropriate event was emitted + System::assert_last_event( + Event::AllBalanceUnstakedAndTransferredToNewColdkey { + current_coldkey, + new_coldkey, + hotkey, + current_stake: 800, + total_balance: 1000, + } + .into(), + ); + }); +} From 65861567aea0431fa8986a527aabe86ff9113486 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:08:30 +0400 Subject: [PATCH 070/185] Update pallets/subtensor/tests/swap.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 65c9f2d4b..1ffd61845 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1172,7 +1172,10 @@ fn test_swap_stake_for_coldkey() { assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); assert!(!Stake::::contains_key(hotkey1, old_coldkey)); assert!(!Stake::::contains_key(hotkey2, old_coldkey)); - +assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); +assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); +assert_eq!(TotalStake::::get(), stake_amount1 + stake_amount2); +assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); // Verify weight update let expected_weight = ::DbWeight::get().reads_writes(3, 4); assert_eq!(weight, expected_weight); From e6b8aabf3798dba1743d12dc58a5d62f72d5205e Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:08:58 +0400 Subject: [PATCH 071/185] Update pallets/subtensor/tests/swap.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 1ffd61845..5a7d9b97b 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1108,7 +1108,7 @@ fn test_do_swap_coldkey_success() { assert!(!OwnedHotkeys::::contains_key(old_coldkey)); // Verify balance transfer - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance + balance_new_coldkey); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); // Verify event emission From c4a1e6eb6f2e836afe8383d40ead121a09626898 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:13:34 +0400 Subject: [PATCH 072/185] Update pallets/subtensor/tests/swap.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 5a7d9b97b..aecb0f7dd 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1085,7 +1085,7 @@ fn test_do_swap_coldkey_success() { assert_eq!(Stake::::get(hotkey, old_coldkey), stake_amount); assert_eq!(OwnedHotkeys::::get(old_coldkey), vec![hotkey]); - assert!(!OwnedHotkeys::::contains_key(new_coldkey)); + assert!(!OwnedHotkeys::::get(new_coldkey).contains(hotkey)); // Get coldkey free balance before swap let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); From d70ef060998ab80208177b96880fabddc9a738c8 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Thu, 4 Jul 2024 23:18:47 +0400 Subject: [PATCH 073/185] Update pallets/subtensor/tests/swap.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index aecb0f7dd..679393b6d 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1091,6 +1091,8 @@ fn test_do_swap_coldkey_success() { let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); assert_eq!(balance, free_balance); +let balance_new_coldkey = SubtensorModule::get_coldkey_balance(&new_coldkey); + // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), From 7062a3e5263128c3f8ee65e391427db0409d33d2 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Thu, 4 Jul 2024 23:31:49 +0400 Subject: [PATCH 074/185] chore: additional tests --- pallets/subtensor/src/swap.rs | 1 + pallets/subtensor/tests/swap.rs | 95 ++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index b6aed8b72..49af72a3f 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -128,6 +128,7 @@ impl Pallet { let block: u64 = Self::get_current_block_as_u64(); // Swap coldkey references in storage maps + // NOTE The order of these calls is important Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); Self::swap_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 679393b6d..324ca3f10 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1054,44 +1054,51 @@ fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let hotkey = U256::from(3); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); let netuid = 1u16; - let stake_amount = 1000u64; - let free_balance = 12345; + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let free_balance_old = 12345u64; // Setup initial state add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, old_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + free_balance); + register_ok_neuron(netuid, hotkey1, old_coldkey, 0); + register_ok_neuron(netuid, hotkey2, old_coldkey, 0); - // Add stake to the neuron + // Add balance to old coldkey + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + free_balance_old, + ); + + // Add stake to the neurons + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey1, + stake_amount1 + )); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(old_coldkey), - hotkey, - stake_amount + hotkey2, + stake_amount2 )); - log::info!( - "TotalColdkeyStake::::get(old_coldkey): {:?}", - TotalColdkeyStake::::get(old_coldkey) + // Verify initial stakes and balances + assert_eq!( + TotalColdkeyStake::::get(old_coldkey), + stake_amount1 + stake_amount2 ); - log::info!( - "Stake::::get(old_coldkey, hotkey): {:?}", - Stake::::get(hotkey, old_coldkey) + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1); + assert_eq!(Stake::::get(hotkey2, old_coldkey), stake_amount2); + assert_eq!( + OwnedHotkeys::::get(old_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!( + SubtensorModule::get_coldkey_balance(&old_coldkey), + free_balance_old ); - - // Verify initial stake - assert_eq!(TotalColdkeyStake::::get(old_coldkey), stake_amount); - assert_eq!(Stake::::get(hotkey, old_coldkey), stake_amount); - - assert_eq!(OwnedHotkeys::::get(old_coldkey), vec![hotkey]); - assert!(!OwnedHotkeys::::get(new_coldkey).contains(hotkey)); - - // Get coldkey free balance before swap - let balance = SubtensorModule::get_coldkey_balance(&old_coldkey); - assert_eq!(balance, free_balance); - -let balance_new_coldkey = SubtensorModule::get_coldkey_balance(&new_coldkey); // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( @@ -1101,16 +1108,30 @@ let balance_new_coldkey = SubtensorModule::get_coldkey_balance(&new_coldkey); )); // Verify the swap - assert_eq!(Owner::::get(hotkey), new_coldkey); - assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake_amount); + assert_eq!(Owner::::get(hotkey1), new_coldkey); + assert_eq!(Owner::::get(hotkey2), new_coldkey); + assert_eq!( + TotalColdkeyStake::::get(new_coldkey), + stake_amount1 + stake_amount2 + ); assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); - assert_eq!(Stake::::get(hotkey, new_coldkey), stake_amount); - assert!(!Stake::::contains_key(hotkey, old_coldkey)); - assert_eq!(OwnedHotkeys::::get(new_coldkey), vec![hotkey]); + assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); + assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); + assert!(!Stake::::contains_key(hotkey1, old_coldkey)); + assert!(!Stake::::contains_key(hotkey2, old_coldkey)); + + // Verify OwnedHotkeys + let new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + assert!(new_owned_hotkeys.contains(&hotkey1)); + assert!(new_owned_hotkeys.contains(&hotkey2)); + assert_eq!(new_owned_hotkeys.len(), 2); assert!(!OwnedHotkeys::::contains_key(old_coldkey)); // Verify balance transfer - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance + balance_new_coldkey); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey), + free_balance_old + ); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); // Verify event emission @@ -1174,10 +1195,10 @@ fn test_swap_stake_for_coldkey() { assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); assert!(!Stake::::contains_key(hotkey1, old_coldkey)); assert!(!Stake::::contains_key(hotkey2, old_coldkey)); -assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); -assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); -assert_eq!(TotalStake::::get(), stake_amount1 + stake_amount2); -assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); + assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); + assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); + assert_eq!(TotalStake::::get(), stake_amount1 + stake_amount2); + assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); // Verify weight update let expected_weight = ::DbWeight::get().reads_writes(3, 4); assert_eq!(weight, expected_weight); From a0fa527c4fba9b951771844e2230b5819f7710b1 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 5 Jul 2024 01:20:40 +0400 Subject: [PATCH 075/185] chore: fix tests --- pallets/subtensor/tests/swap.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 324ca3f10..3d7454fb7 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1184,6 +1184,14 @@ fn test_swap_stake_for_coldkey() { Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); + // Initialize TotalHotkeyStake + TotalHotkeyStake::::insert(hotkey1, stake_amount1); + TotalHotkeyStake::::insert(hotkey2, stake_amount2); + + // Initialize TotalStake and TotalIssuance + TotalStake::::put(stake_amount1 + stake_amount2); + TotalIssuance::::put(stake_amount1 + stake_amount2); + // Populate OwnedHotkeys map OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); @@ -1195,10 +1203,15 @@ fn test_swap_stake_for_coldkey() { assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); assert!(!Stake::::contains_key(hotkey1, old_coldkey)); assert!(!Stake::::contains_key(hotkey2, old_coldkey)); + + // Verify TotalHotkeyStake remains unchanged assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); + + // Verify TotalStake and TotalIssuance remain unchanged assert_eq!(TotalStake::::get(), stake_amount1 + stake_amount2); assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); + // Verify weight update let expected_weight = ::DbWeight::get().reads_writes(3, 4); assert_eq!(weight, expected_weight); From 94b0954cab772a2cc8c2ac6bc85aef34284f839f Mon Sep 17 00:00:00 2001 From: const Date: Thu, 4 Jul 2024 19:17:09 -0500 Subject: [PATCH 076/185] adds tests fixes remove hotkey --- pallets/subtensor/src/events.rs | 4 -- pallets/subtensor/src/lib.rs | 3 +- pallets/subtensor/src/staking.rs | 52 ++++++++--------- pallets/subtensor/tests/staking.rs | 89 +++++++++++++++--------------- runtime/src/lib.rs | 2 +- 5 files changed, 72 insertions(+), 78 deletions(-) diff --git a/pallets/subtensor/src/events.rs b/pallets/subtensor/src/events.rs index 167f10170..d401eebe1 100644 --- a/pallets/subtensor/src/events.rs +++ b/pallets/subtensor/src/events.rs @@ -145,10 +145,6 @@ mod events { current_coldkey: T::AccountId, /// The account ID of the new coldkey new_coldkey: T::AccountId, - /// The account ID of the hotkey - hotkey: T::AccountId, - /// The current stake of the hotkey - current_stake: u64, /// The total balance of the hotkey total_balance: <::Currency as fungible::Inspect< ::AccountId, diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 873168d65..1267c5cc6 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2030,11 +2030,10 @@ pub mod pallet { .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] pub fn unstake_all_and_transfer_to_new_coldkey( origin: OriginFor, - hotkey: T::AccountId, new_coldkey: T::AccountId, ) -> DispatchResult { let current_coldkey = ensure_signed(origin)?; - Self::do_unstake_all_and_transfer_to_new_coldkey(current_coldkey, hotkey, new_coldkey) + Self::do_unstake_all_and_transfer_to_new_coldkey(current_coldkey, new_coldkey) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 6c87f1131..a14db23ed 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -842,7 +842,6 @@ impl Pallet { /// # Arguments /// /// * `current_coldkey` - The AccountId of the current coldkey. - /// * `hotkey` - The AccountId of the hotkey whose balance is being unstaked and transferred. /// * `new_coldkey` - The AccountId of the new coldkey to receive the unstaked tokens. /// /// # Returns @@ -865,36 +864,39 @@ impl Pallet { /// pub fn do_unstake_all_and_transfer_to_new_coldkey( current_coldkey: T::AccountId, - hotkey: T::AccountId, new_coldkey: T::AccountId, ) -> DispatchResult { - // Ensure the hotkey exists and is owned by the current coldkey - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - ensure!( - Self::coldkey_owns_hotkey(¤t_coldkey, &hotkey), - Error::::NonAssociatedColdKey - ); - + // Ensure the new coldkey is different from the current one ensure!(current_coldkey != new_coldkey, Error::::SameColdkey); - // Get the current stake - let current_stake: u64 = Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &hotkey); + // Get all the hotkeys associated with this coldkey + let hotkeys: Vec = OwnedHotkeys::::get(¤t_coldkey); - // Unstake all balance if there's any stake - if current_stake > 0 { - Self::do_remove_stake( - RawOrigin::Signed(current_coldkey.clone()).into(), - hotkey.clone(), - current_stake, - )?; - } + // iterate over all hotkeys. + for next_hotkey in hotkeys { + ensure!( + Self::hotkey_account_exists(&next_hotkey), + Error::::HotKeyAccountNotExists + ); + ensure!( + Self::coldkey_owns_hotkey(¤t_coldkey, &next_hotkey), + Error::::NonAssociatedColdKey + ); - // Get the total balance of the current coldkey account - // let total_balance: <::Currency as fungible::Inspect<::AccountId>>::Balance = T::Currency::total_balance(¤t_coldkey); + // Get the current stake + let current_stake: u64 = Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &next_hotkey); + + // Unstake all balance if there's any stake + if current_stake > 0 { + Self::do_remove_stake( + RawOrigin::Signed(current_coldkey.clone()).into(), + next_hotkey.clone(), + current_stake, + )?; + } + + } let total_balance = Self::get_coldkey_balance(¤t_coldkey); log::info!("Total Bank Balance: {:?}", total_balance); @@ -914,8 +916,6 @@ impl Pallet { Self::deposit_event(Event::AllBalanceUnstakedAndTransferredToNewColdkey { current_coldkey: current_coldkey.clone(), new_coldkey: new_coldkey.clone(), - hotkey: hotkey.clone(), - current_stake, total_balance, }); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index d4441c7c9..1dba153da 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3160,7 +3160,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( current_coldkey, - hotkey, new_coldkey )); @@ -3175,8 +3174,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { Event::AllBalanceUnstakedAndTransferredToNewColdkey { current_coldkey, new_coldkey, - hotkey, - current_stake: 500, total_balance: 1000, } .into(), @@ -3184,40 +3181,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { }); } -#[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_hotkey_not_exists() { - new_test_ext(1).execute_with(|| { - let current_coldkey = U256::from(1); - let hotkey = U256::from(2); - let new_coldkey = U256::from(3); - - assert_err!( - SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - hotkey, - new_coldkey - ), - Error::::HotKeyAccountNotExists - ); - }); -} - -#[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_non_associated_coldkey() { - new_test_ext(1).execute_with(|| { - let (_, hotkey, new_coldkey) = setup_test_environment(); - let wrong_coldkey = U256::from(4); - - assert_noop!( - SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - wrong_coldkey, - hotkey, - new_coldkey - ), - Error::::NonAssociatedColdKey - ); - }); -} #[test] fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { @@ -3227,7 +3190,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { assert_noop!( SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( current_coldkey, - hotkey, current_coldkey ), Error::::SameColdkey @@ -3270,7 +3232,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { // Try to unstake and transfer let result = SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( current_coldkey, - hotkey, new_coldkey, ); @@ -3343,7 +3304,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { // Perform unstake and transfer assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( current_coldkey, - hotkey, new_coldkey )); @@ -3370,8 +3330,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { Event::AllBalanceUnstakedAndTransferredToNewColdkey { current_coldkey, new_coldkey, - hotkey, - current_stake: 0, total_balance: initial_balance, } .into(), @@ -3394,7 +3352,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( current_coldkey, - hotkey, new_coldkey )); @@ -3409,11 +3366,53 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { Event::AllBalanceUnstakedAndTransferredToNewColdkey { current_coldkey, new_coldkey, - hotkey, - current_stake: 800, total_balance: 1000, } .into(), ); }); } + + +#[test] +fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple() { + new_test_ext(1).execute_with(|| { + + // Register the neuron to a new network + let netuid = 1; + let hotkey0 = U256::from(1); + let hotkey2 = U256::from(2); + let current_coldkey = U256::from(3); + let new_coldkey = U256::from(4); + add_network(netuid, 0, 0); + register_ok_neuron(1, hotkey0, current_coldkey, 0); + register_ok_neuron(1, hotkey2, current_coldkey, 0); + SubtensorModule::set_target_stakes_per_interval(10); + SubtensorModule::add_balance_to_coldkey_account(¤t_coldkey, 1000); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(current_coldkey), + hotkey0, + 500 + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(current_coldkey), + hotkey2, + 300 + )); + assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + current_coldkey, + new_coldkey + )); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 0); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 0); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); + System::assert_last_event( + Event::AllBalanceUnstakedAndTransferredToNewColdkey { + current_coldkey, + new_coldkey, + total_balance: 1000, + } + .into(), + ); + }); +} \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 44b4d9b00..273ebef83 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 156, + spec_version: 158, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 5a263489ce03984f779e4c34ec16839a56312442 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 5 Jul 2024 04:21:06 +0400 Subject: [PATCH 077/185] lints --- pallets/subtensor/src/staking.rs | 5 ++--- pallets/subtensor/tests/staking.rs | 7 ++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index a14db23ed..09f6d68ac 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -866,7 +866,6 @@ impl Pallet { current_coldkey: T::AccountId, new_coldkey: T::AccountId, ) -> DispatchResult { - // Ensure the new coldkey is different from the current one ensure!(current_coldkey != new_coldkey, Error::::SameColdkey); @@ -885,7 +884,8 @@ impl Pallet { ); // Get the current stake - let current_stake: u64 = Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &next_hotkey); + let current_stake: u64 = + Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &next_hotkey); // Unstake all balance if there's any stake if current_stake > 0 { @@ -895,7 +895,6 @@ impl Pallet { current_stake, )?; } - } let total_balance = Self::get_coldkey_balance(¤t_coldkey); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 1dba153da..4030a0f5c 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3181,11 +3181,10 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { }); } - #[test] fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { new_test_ext(1).execute_with(|| { - let (current_coldkey, hotkey, _) = setup_test_environment(); + let (current_coldkey, _hotkey, _) = setup_test_environment(); assert_noop!( SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( @@ -3373,11 +3372,9 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { }); } - #[test] fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple() { new_test_ext(1).execute_with(|| { - // Register the neuron to a new network let netuid = 1; let hotkey0 = U256::from(1); @@ -3415,4 +3412,4 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple .into(), ); }); -} \ No newline at end of file +} From dbea365520569dc36ee570684f9fec09f112fa8a Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 5 Jul 2024 13:28:21 -0400 Subject: [PATCH 078/185] Add migration to populate StakingHotkeys, unstake delegations in do_unstake_all_and_transfer_to_new_coldkey --- pallets/subtensor/src/lib.rs | 14 ++++++- pallets/subtensor/src/migration.rs | 63 ++++++++++++++++++++++++++++++ pallets/subtensor/src/staking.rs | 40 +++++++++++++++++++ pallets/subtensor/src/swap.rs | 17 +++++++- pallets/subtensor/tests/swap.rs | 4 +- 5 files changed, 134 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 1267c5cc6..c4b0c74cd 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -380,6 +380,9 @@ pub mod pallet { ValueQuery, DefaultAccountTake, >; + #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + pub type StakingHotkeys = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; /// -- ITEM (switches liquid alpha on) #[pallet::type_value] pub fn DefaultLiquidAlpha() -> bool { @@ -1225,6 +1228,13 @@ pub mod pallet { Stake::::insert(hotkey.clone(), coldkey.clone(), stake); + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } + next_uid = next_uid.checked_add(1).expect( "should not have total number of hotkey accounts larger than u16::MAX", ); @@ -1337,7 +1347,9 @@ pub mod pallet { // Doesn't check storage version. TODO: Remove after upgrade .saturating_add(migration::migration5_total_issuance::(false)) // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. - .saturating_add(migration::migrate_populate_owned::()); + .saturating_add(migration::migrate_populate_owned::()) + // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. + .saturating_add(migration::migrate_populate_staking_hotkeys::()); weight } diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 5dbdd5f42..06ecb91df 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -539,3 +539,66 @@ pub fn migrate_populate_owned() -> Weight { Weight::zero() } } + +/// Populate the StakingHotkeys map from Stake map +pub fn migrate_populate_staking_hotkeys() -> Weight { + // Setup migration weight + let mut weight = T::DbWeight::get().reads(1); + let migration_name = "Populate StakingHotkeys map"; + + // Check if this migration is needed (if StakingHotkeys map is empty) + let migrate = StakingHotkeys::::iter().next().is_none(); + + // Only runs if the migration is needed + if migrate { + info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); + + let mut longest_hotkey_vector: usize = 0; + let mut longest_coldkey: Option = None; + let mut keys_touched: u64 = 0; + let mut storage_reads: u64 = 0; + let mut storage_writes: u64 = 0; + + // Iterate through all Owner entries + Stake::::iter().for_each(|(hotkey, coldkey, _stake)| { + storage_reads = storage_reads.saturating_add(1); // Read from Owner storage + let mut hotkeys = StakingHotkeys::::get(&coldkey); + storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage + + // Add the hotkey if it's not already in the vector + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + keys_touched = keys_touched.saturating_add(1); + + // Update longest hotkey vector info + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } + + // Update the StakingHotkeys storage + StakingHotkeys::::insert(&coldkey, hotkeys); + storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage + } + + // Accrue weight for reads and writes + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); + }); + + // Log migration results + info!( + target: LOG_TARGET_1, + "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", + migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes + ); + if let Some(c) = longest_coldkey { + info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); + } + + weight + } else { + info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); + Weight::zero() + } +} + diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 09f6d68ac..59f8ca4de 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -569,6 +569,13 @@ impl Pallet { hotkeys.push(hotkey.clone()); OwnedHotkeys::::insert(coldkey, hotkeys); } + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } } } @@ -648,6 +655,13 @@ impl Pallet { Stake::::get(hotkey, coldkey).saturating_add(increment), ); TotalStake::::put(TotalStake::::get().saturating_add(increment)); + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } } // Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. @@ -668,6 +682,8 @@ impl Pallet { Stake::::get(hotkey, coldkey).saturating_sub(decrement), ); TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); + + // TODO: Tech debt: Remove StakingHotkeys entry if stake goes to 0 } /// Empties the stake associated with a given coldkey-hotkey account pairing. @@ -693,6 +709,11 @@ impl Pallet { TotalStake::::mutate(|stake| *stake = stake.saturating_sub(current_stake)); TotalIssuance::::mutate(|issuance| *issuance = issuance.saturating_sub(current_stake)); + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + staking_hotkeys.retain(|h| h != hotkey); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + current_stake } @@ -897,6 +918,25 @@ impl Pallet { } } + // Unstake all delegate stake make by this coldkey to non-owned hotkeys + let staking_hotkeys = StakingHotkeys::::get(¤t_coldkey); + + // iterate over all staking hotkeys. + for hotkey in staking_hotkeys { + // Get the current stake + let current_stake: u64 = + Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &hotkey); + + // Unstake all balance if there's any stake + if current_stake > 0 { + Self::do_remove_stake( + RawOrigin::Signed(current_coldkey.clone()).into(), + hotkey.clone(), + current_stake, + )?; + } + } + let total_balance = Self::get_coldkey_balance(¤t_coldkey); log::info!("Total Bank Balance: {:?}", total_balance); diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 49af72a3f..6ea38a94c 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -272,12 +272,22 @@ impl Pallet { for (coldkey, stake_amount) in stakes { Stake::::insert(new_hotkey, &coldkey, stake_amount); writes = writes.saturating_add(1u64); // One write for insert + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); + if !staking_hotkeys.contains(new_hotkey) { + staking_hotkeys.push(new_hotkey.clone()); + StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); + writes = writes.saturating_add(1u64); // One write for insert + } } // Clear the prefix for the old hotkey after transferring all stakes let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); writes = writes.saturating_add(1); // One write for insert; // One write for clear_prefix + // TODO: Remove all entries for old hotkey from StakingHotkeys map + weight.saturating_accrue(T::DbWeight::get().writes(writes)); } @@ -521,7 +531,12 @@ impl Pallet { let stake = Stake::::get(&hotkey, old_coldkey); Stake::::remove(&hotkey, old_coldkey); Stake::::insert(&hotkey, new_coldkey, stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // Update StakingHotkeys map + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + StakingHotkeys::::insert(new_coldkey.clone(), staking_hotkeys); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 3d7454fb7..d527c44e8 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -625,7 +625,7 @@ fn test_swap_stake_weight_update() { SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); + let expected_weight = ::DbWeight::get().writes(3); assert_eq!(weight, expected_weight); }); } @@ -1213,7 +1213,7 @@ fn test_swap_stake_for_coldkey() { assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(3, 4); + let expected_weight = ::DbWeight::get().reads_writes(5, 6); assert_eq!(weight, expected_weight); }); } From f037dbc08c0420e80a1211dd3f5f18e108c66e90 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 5 Jul 2024 13:48:38 -0400 Subject: [PATCH 079/185] Filter migration StakingHotkey by non-zero stakes, bump spec version --- pallets/subtensor/src/migration.rs | 42 ++++++++++++++++-------------- runtime/src/lib.rs | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 06ecb91df..400030450 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -560,29 +560,31 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { let mut storage_writes: u64 = 0; // Iterate through all Owner entries - Stake::::iter().for_each(|(hotkey, coldkey, _stake)| { + Stake::::iter().for_each(|(hotkey, coldkey, stake)| { storage_reads = storage_reads.saturating_add(1); // Read from Owner storage - let mut hotkeys = StakingHotkeys::::get(&coldkey); - storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage - - // Add the hotkey if it's not already in the vector - if !hotkeys.contains(&hotkey) { - hotkeys.push(hotkey); - keys_touched = keys_touched.saturating_add(1); - - // Update longest hotkey vector info - if longest_hotkey_vector < hotkeys.len() { - longest_hotkey_vector = hotkeys.len(); - longest_coldkey = Some(coldkey.clone()); + if stake > 0 { + let mut hotkeys = StakingHotkeys::::get(&coldkey); + storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage + + // Add the hotkey if it's not already in the vector + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + keys_touched = keys_touched.saturating_add(1); + + // Update longest hotkey vector info + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } + + // Update the StakingHotkeys storage + StakingHotkeys::::insert(&coldkey, hotkeys); + storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage } - - // Update the StakingHotkeys storage - StakingHotkeys::::insert(&coldkey, hotkeys); - storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage + + // Accrue weight for reads and writes + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); } - - // Accrue weight for reads and writes - weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); }); // Log migration results diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 273ebef83..71b506247 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 158, + spec_version: 159, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From c66aeb911f4726bfb6c40eca0c2bfe4980f0853e Mon Sep 17 00:00:00 2001 From: const Date: Fri, 5 Jul 2024 16:57:48 -0500 Subject: [PATCH 080/185] initial --- pallets/subtensor/src/lib.rs | 12 +++ pallets/subtensor/src/staking.rs | 156 +++++++++++++++++++++++-------- 2 files changed, 127 insertions(+), 41 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 1267c5cc6..0550a823d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -380,6 +380,18 @@ pub mod pallet { ValueQuery, DefaultAccountTake, >; + + + + #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. + pub type Drain = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + + + #[pallet::storage] // --- MAP ( cold ) --> block_when_drain_occurs | Returns the block when the + pub type Period = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + + + /// -- ITEM (switches liquid alpha on) #[pallet::type_value] pub fn DefaultLiquidAlpha() -> bool { diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 09f6d68ac..552107e91 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -869,55 +869,129 @@ impl Pallet { // Ensure the new coldkey is different from the current one ensure!(current_coldkey != new_coldkey, Error::::SameColdkey); - // Get all the hotkeys associated with this coldkey - let hotkeys: Vec = OwnedHotkeys::::get(¤t_coldkey); + // Get the current wallets to drain to. + let mut drain_wallets: Vec = Drain::::get( ¤t_coldkey ); - // iterate over all hotkeys. - for next_hotkey in hotkeys { - ensure!( - Self::hotkey_account_exists(&next_hotkey), - Error::::HotKeyAccountNotExists - ); - ensure!( - Self::coldkey_owns_hotkey(¤t_coldkey, &next_hotkey), - Error::::NonAssociatedColdKey - ); + // DOS protection: If there are more than 10 wallets to drain to, we extend the period. + // if drain_wallets.len() > 10 { + // // Set the new coldkey as the first drain wallet. + // drain_wallets.push(new_coldkey); + // Drain::::insert(current_coldkey, drain_wallets); + // } + + // Check if the new coldkey is already in the drain wallets list + ensure!( + !drain_wallets.contains(&new_coldkey), + Error::::DuplicateColdkey + ); + + // Extend the period if we have two unique keys in the drain + if drain_wallets.len() > 0 { + + // Get the current block to drain + let mut drain_block: u64 = DrainBlock::::get( current_coldkey ); + + // Extend the block to drain. + let extended_block: u64 = drain_block + 7200 * 7; + + // Set the new drain block. + DrainBlock::::insert( current_coldkey, extended_block ); + + // Extend the period. + let mut coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( extended_block ); + + // Add the coldkey to drain on this block. + coldkeys_to_drain.push( current_coldkey ); + + // Set the new coldkeys to drain here. + ColdkeysToDrainOnBlock::::insert( extended_block, coldkeys_to_drain.clone() ); + + // Clear the pending keys. + Drain::::remove(¤t_coldkey); + + } else { + // There are not other wallets pending. + + // Extend the wallet to drain to. + drain_wallets.push(new_coldkey); + + // Push the change. + Drain::::insert( current_coldkey, drain_wallets ); + } + + Ok(()) + } - // Get the current stake - let current_stake: u64 = - Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &next_hotkey); - - // Unstake all balance if there's any stake - if current_stake > 0 { - Self::do_remove_stake( - RawOrigin::Signed(current_coldkey.clone()).into(), - next_hotkey.clone(), - current_stake, - )?; + pub fn drain_all_pending_coldkeys(block_num){ + + // Get the block number + let current_block: u64 = Self::get_current_block_as_u64(); + + // Get the coldkeys to drain here. + let mut coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( current_block ); + + // Iterate over all keys in Drain and call drain_to_pending_coldkeys for each + for coldkey_i in coldkeys_to_drain.iter() { + + // Get the wallets to drain to for this coldkey. + let wallets_to_drain: Vec = Drain::::get( coldkey_i ); + + // If there are no wallets to drain to, remove the key from the drain map. + if wallets_to_drain.len() == 0 { + + // Remove the key from the drain map + Drain::::remove( &coldkey ); + continue + } + // If there is only 1 wallet to drain perform the drain operation. + if wallets_to_drain.len() == 1 { + + // Get the wallet to drain to. + let wallet_to_drain_to: T::AccountId = wallets_to_drain[0]; + + // Perform the drain. + Self::drain_from_coldkeyA_to_coldkey_B( &coldkey_i, &wallet_to_drain_to ); + + // Remove the key from the drain map + Drain::::remove( &coldkey ); + + // Set the new drain block. + DrainBlock::::remove( &coldkey ); } } + } - let total_balance = Self::get_coldkey_balance(¤t_coldkey); - log::info!("Total Bank Balance: {:?}", total_balance); - // Ensure there's a balance to transfer - ensure!(!total_balance.is_zero(), Error::::NoBalanceToTransfer); + pub fn drain_from_coldkeyA_to_coldkey_B( coldkeyA: &T::AccountId, coldkeyB: &T::AccountId ) { - // Attempt to transfer the entire total balance to the new coldkey - T::Currency::transfer( - ¤t_coldkey, - &new_coldkey, - total_balance, - Preservation::Expendable, - )?; + // Get the hotkeys associated with coldkeyA. + let coldkeyA_hotkeys: Vec = StakingHotkeys::::get( &coldkeyA ); - // Emit the event - Self::deposit_event(Event::AllBalanceUnstakedAndTransferredToNewColdkey { - current_coldkey: current_coldkey.clone(), - new_coldkey: new_coldkey.clone(), - total_balance, - }); + // Iterate over all the hotkeys associated with this coldkey + for hotkey_i in coldkeyA_hotkeys.iter() { + + // Get the current stake from coldkeyA to hotkey_i. + let all_current_stake_i: u64 = Self::get_stake_for_coldkey_and_hotkey( &coldkeyA, &hotkey_i ); + + // We remove the balance from the hotkey acount equal to all of it. + Self::decrease_stake_on_coldkey_hotkey_account( &coldkeyA, &hotkey_i, all_current_stake_i ); + + // We add the balance to the coldkey. If the above fails we will not credit this coldkey. + Self::add_balance_to_coldkey_account( &coldkeyA, all_current_stake_i ); + } + + // Get the total balance here. + let total_balance = Self::get_coldkey_balance( &coldkeyA ); + + if !total_balance.is_zero() { + // Attempt to transfer the entire total balance to coldkeyB. + T::Currency::transfer( + ¤t_coldkey, + &new_coldkey, + total_balance, + Preservation::Expendable, + ); + } - Ok(()) } } From 4a398957de1029c51ef50302df67ace9f778d86b Mon Sep 17 00:00:00 2001 From: const Date: Fri, 5 Jul 2024 16:58:39 -0500 Subject: [PATCH 081/185] merge --- pallets/subtensor/src/staking.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 552107e91..080a34d5b 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -872,13 +872,6 @@ impl Pallet { // Get the current wallets to drain to. let mut drain_wallets: Vec = Drain::::get( ¤t_coldkey ); - // DOS protection: If there are more than 10 wallets to drain to, we extend the period. - // if drain_wallets.len() > 10 { - // // Set the new coldkey as the first drain wallet. - // drain_wallets.push(new_coldkey); - // Drain::::insert(current_coldkey, drain_wallets); - // } - // Check if the new coldkey is already in the drain wallets list ensure!( !drain_wallets.contains(&new_coldkey), From bfea68a65a25012afc3a3cdbcffc7e56d5ff98b6 Mon Sep 17 00:00:00 2001 From: const Date: Fri, 5 Jul 2024 17:50:38 -0500 Subject: [PATCH 082/185] push changes --- pallets/subtensor/src/errors.rs | 2 + pallets/subtensor/src/lib.rs | 26 +++-- pallets/subtensor/src/migration.rs | 65 ++++++++++++ pallets/subtensor/src/registration.rs | 1 + pallets/subtensor/src/root.rs | 7 +- pallets/subtensor/src/staking.rs | 141 +++++++++++++++++--------- pallets/subtensor/src/swap.rs | 21 +++- pallets/subtensor/tests/swap.rs | 4 +- runtime/src/lib.rs | 2 +- 9 files changed, 208 insertions(+), 61 deletions(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index 3e30c094c..bd113be3d 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -146,5 +146,7 @@ mod errors { NoBalanceToTransfer, /// Same coldkey SameColdkey, + /// The coldkey is in arbitration + ColdkeyIsInArbitration, } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0550a823d..6e705b568 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -381,16 +381,17 @@ pub mod pallet { DefaultAccountTake, >; - - + /// Default value for hotkeys. + #[pallet::type_value] + pub fn EmptyAccounts() -> Vec { vec![] } #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. - pub type Drain = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - - - #[pallet::storage] // --- MAP ( cold ) --> block_when_drain_occurs | Returns the block when the - pub type Period = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + pub type ColdkeysToDrainTo = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery, EmptyAccounts>; + #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. + pub type ColdkeysToDrainOnBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; + #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; /// -- ITEM (switches liquid alpha on) #[pallet::type_value] @@ -1237,6 +1238,13 @@ pub mod pallet { Stake::::insert(hotkey.clone(), coldkey.clone(), stake); + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } + next_uid = next_uid.checked_add(1).expect( "should not have total number of hotkey accounts larger than u16::MAX", ); @@ -1349,7 +1357,9 @@ pub mod pallet { // Doesn't check storage version. TODO: Remove after upgrade .saturating_add(migration::migration5_total_issuance::(false)) // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. - .saturating_add(migration::migrate_populate_owned::()); + .saturating_add(migration::migrate_populate_owned::()) + // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. + .saturating_add(migration::migrate_populate_staking_hotkeys::()); weight } diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 5dbdd5f42..400030450 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -539,3 +539,68 @@ pub fn migrate_populate_owned() -> Weight { Weight::zero() } } + +/// Populate the StakingHotkeys map from Stake map +pub fn migrate_populate_staking_hotkeys() -> Weight { + // Setup migration weight + let mut weight = T::DbWeight::get().reads(1); + let migration_name = "Populate StakingHotkeys map"; + + // Check if this migration is needed (if StakingHotkeys map is empty) + let migrate = StakingHotkeys::::iter().next().is_none(); + + // Only runs if the migration is needed + if migrate { + info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); + + let mut longest_hotkey_vector: usize = 0; + let mut longest_coldkey: Option = None; + let mut keys_touched: u64 = 0; + let mut storage_reads: u64 = 0; + let mut storage_writes: u64 = 0; + + // Iterate through all Owner entries + Stake::::iter().for_each(|(hotkey, coldkey, stake)| { + storage_reads = storage_reads.saturating_add(1); // Read from Owner storage + if stake > 0 { + let mut hotkeys = StakingHotkeys::::get(&coldkey); + storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage + + // Add the hotkey if it's not already in the vector + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + keys_touched = keys_touched.saturating_add(1); + + // Update longest hotkey vector info + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } + + // Update the StakingHotkeys storage + StakingHotkeys::::insert(&coldkey, hotkeys); + storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage + } + + // Accrue weight for reads and writes + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); + } + }); + + // Log migration results + info!( + target: LOG_TARGET_1, + "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", + migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes + ); + if let Some(c) = longest_coldkey { + info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); + } + + weight + } else { + info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); + Weight::zero() + } +} + diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 4688bcbb5..bd162a5cf 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -41,6 +41,7 @@ impl Pallet { ) -> DispatchResult { // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) let coldkey = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_registration( coldkey:{:?} netuid:{:?} hotkey:{:?} )", coldkey, diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index 9e0327fb3..e65524909 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -483,6 +483,7 @@ impl Pallet { // --- 1. Ensure that the call originates from a signed source and retrieve the caller's account ID (coldkey). let coldkey = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_root_register( coldkey: {:?}, hotkey: {:?} )", coldkey, @@ -623,6 +624,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // Check the caller's signature. This is the coldkey of a registered account. let coldkey = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_set_root_weights( origin:{:?} netuid:{:?}, uids:{:?}, values:{:?})", coldkey, @@ -744,6 +746,7 @@ impl Pallet { ) -> DispatchResultWithPostInfo { // --- 1. Ensure that the caller has signed with their coldkey. let coldkey = ensure_signed(origin.clone())?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure that the calling coldkey owns the associated hotkey. ensure!( @@ -797,7 +800,8 @@ impl Pallet { pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; - + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + // --- 1. Rate limit for network registrations. let current_block = Self::get_current_block_as_u64(); let last_lock_block = Self::get_network_last_lock_block(); @@ -885,6 +889,7 @@ impl Pallet { pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { // --- 1. Ensure the function caller is a signed user. let coldkey = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure this subnet exists. ensure!( diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 080a34d5b..3deef551b 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -46,6 +46,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signuture. let coldkey = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, @@ -135,6 +136,7 @@ impl Pallet { hotkey, take ); + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -207,6 +209,7 @@ impl Pallet { hotkey, take ); + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -292,6 +295,7 @@ impl Pallet { hotkey, stake_to_be_added ); + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); // Ensure the callers coldkey has enough stake to perform the transaction. ensure!( @@ -404,6 +408,7 @@ impl Pallet { hotkey, stake_to_be_removed ); + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); // Ensure that the hotkey account exists this is only possible through registration. ensure!( @@ -569,6 +574,13 @@ impl Pallet { hotkeys.push(hotkey.clone()); OwnedHotkeys::::insert(coldkey, hotkeys); } + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } } } @@ -648,6 +660,13 @@ impl Pallet { Stake::::get(hotkey, coldkey).saturating_add(increment), ); TotalStake::::put(TotalStake::::get().saturating_add(increment)); + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } } // Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. @@ -668,6 +687,8 @@ impl Pallet { Stake::::get(hotkey, coldkey).saturating_sub(decrement), ); TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); + + // TODO: Tech debt: Remove StakingHotkeys entry if stake goes to 0 } /// Empties the stake associated with a given coldkey-hotkey account pairing. @@ -693,6 +714,11 @@ impl Pallet { TotalStake::::mutate(|stake| *stake = stake.saturating_sub(current_stake)); TotalIssuance::::mutate(|issuance| *issuance = issuance.saturating_sub(current_stake)); + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + staking_hotkeys.retain(|h| h != hotkey); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + current_stake } @@ -866,90 +892,108 @@ impl Pallet { current_coldkey: T::AccountId, new_coldkey: T::AccountId, ) -> DispatchResult { - // Ensure the new coldkey is different from the current one - ensure!(current_coldkey != new_coldkey, Error::::SameColdkey); // Get the current wallets to drain to. - let mut drain_wallets: Vec = Drain::::get( ¤t_coldkey ); + let mut coldkeys_to_drain_to: Vec = ColdkeyToDrainTo::::get( ¤t_coldkey ); // Check if the new coldkey is already in the drain wallets list ensure!( - !drain_wallets.contains(&new_coldkey), + !coldkeys_to_drain_to.contains( &new_coldkey ), Error::::DuplicateColdkey ); - // Extend the period if we have two unique keys in the drain - if drain_wallets.len() > 0 { - - // Get the current block to drain - let mut drain_block: u64 = DrainBlock::::get( current_coldkey ); + // Add the wallet to the drain wallets. + if coldkeys_to_drain_to.len() == 0 || coldkeys_to_drain_to.len() == 1 { - // Extend the block to drain. - let extended_block: u64 = drain_block + 7200 * 7; - - // Set the new drain block. - DrainBlock::::insert( current_coldkey, extended_block ); + // Extend the wallet to drain to. + coldkeys_to_drain_to.push(new_coldkey); - // Extend the period. - let mut coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( extended_block ); + // Push the change. + ColdkeysToDrainTo::::insert( current_coldkey, drain_wallets ); + } - // Add the coldkey to drain on this block. - coldkeys_to_drain.push( current_coldkey ); + // If this is the first time we have seen this key we will put the drain period to be in 1 week. + if coldkeys_to_drain_to.len() == 0 { - // Set the new coldkeys to drain here. - ColdkeysToDrainOnBlock::::insert( extended_block, coldkeys_to_drain.clone() ); + // Get the current block. + let current_block: u64 = Self::get_current_block_as_u64(); - // Clear the pending keys. - Drain::::remove(¤t_coldkey); + // Next arbitrage period + let next_arbitrage_period: u64 = current_block + 7200 * 7; - } else { - // There are not other wallets pending. + // First time seeing this key lets push the drain moment to 1 week in the future. + let mut next_period_coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( next_arbitrage_period ); - // Extend the wallet to drain to. - drain_wallets.push(new_coldkey); + // Add this new coldkey to these coldkeys + // Sanity Check. + if !next_period_coldkeys_to_drain.contains(&coldkey_i) { + next_period_coldkeys_to_drain.push(coldkey_i); + } - // Push the change. - Drain::::insert( current_coldkey, drain_wallets ); + // Set the new coldkeys to drain here. + ColdkeysToDrainOnBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_drain ); } + // Return true. Ok(()) } - pub fn drain_all_pending_coldkeys(block_num){ + + // Chain opens, this is the only allowed transaction. + // 1. Someone calls drain with key C1, DestA, DestA is added to ColdToBeDrained and C1 is given a drain block in 7 days. + // 2. Someone else calls drain with key C1, DestB, DestB is added to ColdToBeDrained and C1 already has a drain block, not updated. + // 3. Someone calls drain key with C1, DestC. Dest C is not added to the ColdToBeDrained. No-op/ + // 4. 7200 * 7 blocks progress. + // 5. ColdkeysToDrainOnBlock( block ) returns [ C1 ] + // 6. ColdToBeDrained( C1 ) returns [ DestA, DestB ] and is Drained + // 7. len([ DestA, DestB ]) == 2, set the drain block to be 7 days in the future. + // 8. Someone calls drain with key C1, DestA, DestD is added to ColdToBeDrained and C1 is given a drain block in 7 days. + // 9. 7200 * 7 blocks progress. + // 10. ColdkeysToDrainOnBlock( block ) returns [ C1 ] + // 11. ColdToBeDrained( C1 ) returns [ DestD ] and is Drained + // 12. len([ DestD ]) == 1, call_drain( C1, DestD ) + + pub fn drain_all_pending_coldkeys() { // Get the block number let current_block: u64 = Self::get_current_block_as_u64(); // Get the coldkeys to drain here. - let mut coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( current_block ); + let mut source_coldkeys: Vec = ColdkeysToDrainOnBlock::::get( current_block ); + ColdkeysToDrainOnBlock::::remove( current_block ); // Iterate over all keys in Drain and call drain_to_pending_coldkeys for each - for coldkey_i in coldkeys_to_drain.iter() { + for coldkey_i in source_coldkeys.iter() { // Get the wallets to drain to for this coldkey. - let wallets_to_drain: Vec = Drain::::get( coldkey_i ); + if !ColdkeysToDrainTo::::contains_key( &coldkey_i ) { continue } // Sanity Check. + let destinations_coldkeys: Vec = ColdkeysToDrainTo::::get( coldkey_i ); + ColdkeysToDrainTo::::remove( &coldkey_i ); - // If there are no wallets to drain to, remove the key from the drain map. - if wallets_to_drain.len() == 0 { + // If the wallets to drain is > 1, we extend the period. + if destinations_coldkeys.len() > 1 { - // Remove the key from the drain map - Drain::::remove( &coldkey ); - continue - } - // If there is only 1 wallet to drain perform the drain operation. - if wallets_to_drain.len() == 1 { + // Next arbitrage period + let next_arbitrage_period: u64 = current_block + 7200 * 30; - // Get the wallet to drain to. + // Get the coldkeys to drain at the next arbitrage period. + let mut next_period_coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( next_arbitrage_period ); + + // Add this new coldkey to these coldkeys + // Sanity Check. + if !next_period_coldkeys_to_drain.contains(&coldkey_i) { + next_period_coldkeys_to_drain.push(coldkey_i); + } + + // Set the new coldkeys to drain here. + ColdkeysToDrainOnBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_drain ); + + } else if destinations_coldkeys.len() == 1 { + // ONLY 1 wallet: Get the wallet to drain to. let wallet_to_drain_to: T::AccountId = wallets_to_drain[0]; // Perform the drain. Self::drain_from_coldkeyA_to_coldkey_B( &coldkey_i, &wallet_to_drain_to ); - - // Remove the key from the drain map - Drain::::remove( &coldkey ); - - // Set the new drain block. - DrainBlock::::remove( &coldkey ); } } } @@ -985,6 +1029,9 @@ impl Pallet { Preservation::Expendable, ); } + } + pub fn coldkey_is_locked( coldkey: &T::AccountId ) -> bool { + ColdkeyToDrainTo::::contains_key( &coldkey ) } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 49af72a3f..137956dc3 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -28,6 +28,7 @@ impl Pallet { new_hotkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); let mut weight = T::DbWeight::get().reads(2); @@ -115,7 +116,8 @@ impl Pallet { old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { - ensure_signed(origin)?; + let coldkey_performing_swap = ensure_signed(origin)?; + ensure!(!Self::coldkey_is_locked(&coldkey_performing_swap), Error::::ColdkeyIsInArbitration); let mut weight = T::DbWeight::get().reads(2); @@ -272,12 +274,22 @@ impl Pallet { for (coldkey, stake_amount) in stakes { Stake::::insert(new_hotkey, &coldkey, stake_amount); writes = writes.saturating_add(1u64); // One write for insert + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); + if !staking_hotkeys.contains(new_hotkey) { + staking_hotkeys.push(new_hotkey.clone()); + StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); + writes = writes.saturating_add(1u64); // One write for insert + } } // Clear the prefix for the old hotkey after transferring all stakes let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); writes = writes.saturating_add(1); // One write for insert; // One write for clear_prefix + // TODO: Remove all entries for old hotkey from StakingHotkeys map + weight.saturating_accrue(T::DbWeight::get().writes(writes)); } @@ -521,7 +533,12 @@ impl Pallet { let stake = Stake::::get(&hotkey, old_coldkey); Stake::::remove(&hotkey, old_coldkey); Stake::::insert(&hotkey, new_coldkey, stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // Update StakingHotkeys map + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + StakingHotkeys::::insert(new_coldkey.clone(), staking_hotkeys); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 3d7454fb7..d527c44e8 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -625,7 +625,7 @@ fn test_swap_stake_weight_update() { SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); + let expected_weight = ::DbWeight::get().writes(3); assert_eq!(weight, expected_weight); }); } @@ -1213,7 +1213,7 @@ fn test_swap_stake_for_coldkey() { assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(3, 4); + let expected_weight = ::DbWeight::get().reads_writes(5, 6); assert_eq!(weight, expected_weight); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 273ebef83..71b506247 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 158, + spec_version: 159, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 462ed12b26144c6910e4af5d4675818b3d17f310 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 5 Jul 2024 19:35:31 -0400 Subject: [PATCH 083/185] Fix build and straighten up the logic for draining coldkeys --- pallets/subtensor/src/errors.rs | 2 + pallets/subtensor/src/lib.rs | 7 ++- pallets/subtensor/src/staking.rs | 85 ++++++++++++++++++------------ pallets/subtensor/tests/staking.rs | 24 ++++----- 4 files changed, 72 insertions(+), 46 deletions(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index bd113be3d..ed0afb184 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -148,5 +148,7 @@ mod errors { SameColdkey, /// The coldkey is in arbitration ColdkeyIsInArbitration, + /// The new coldkey is already registered for the drain + DuplicateColdkey, } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6e705b568..d8cdc81dc 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1311,6 +1311,9 @@ pub mod pallet { // * 'n': (BlockNumberFor): // - The number of the block we are initializing. fn on_initialize(_block_number: BlockNumberFor) -> Weight { + // Unstake all and transfer pending coldkeys + let drain_weight = Self::drain_all_pending_coldkeys(); + let block_step_result = Self::block_step(); match block_step_result { Ok(_) => { @@ -1319,6 +1322,7 @@ pub mod pallet { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) + .saturating_add(drain_weight) } Err(e) => { // --- If the block step was unsuccessful, return the weight anyway. @@ -1326,6 +1330,7 @@ pub mod pallet { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) + .saturating_add(drain_weight) } } } @@ -2055,7 +2060,7 @@ pub mod pallet { new_coldkey: T::AccountId, ) -> DispatchResult { let current_coldkey = ensure_signed(origin)?; - Self::do_unstake_all_and_transfer_to_new_coldkey(current_coldkey, new_coldkey) + Self::do_unstake_all_and_transfer_to_new_coldkey(¤t_coldkey, &new_coldkey) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 3deef551b..11bcbf564 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -1,5 +1,4 @@ use super::*; -use dispatch::RawOrigin; use frame_support::{ storage::IterableStorageDoubleMap, traits::{ @@ -8,9 +7,10 @@ use frame_support::{ Fortitude, Precision, Preservation, }, Imbalance, - }, + }, weights::Weight, }; use num_traits::Zero; +use sp_core::Get; impl Pallet { /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. @@ -889,31 +889,33 @@ impl Pallet { /// Emits a `PartialBalanceTransferredToNewColdkey` event if only a partial transfer is successful. /// pub fn do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey: T::AccountId, - new_coldkey: T::AccountId, + current_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, ) -> DispatchResult { // Get the current wallets to drain to. - let mut coldkeys_to_drain_to: Vec = ColdkeyToDrainTo::::get( ¤t_coldkey ); + let mut coldkeys_to_drain_to: Vec = ColdkeysToDrainTo::::get( current_coldkey ); // Check if the new coldkey is already in the drain wallets list ensure!( - !coldkeys_to_drain_to.contains( &new_coldkey ), + !coldkeys_to_drain_to.contains( new_coldkey ), Error::::DuplicateColdkey ); // Add the wallet to the drain wallets. - if coldkeys_to_drain_to.len() == 0 || coldkeys_to_drain_to.len() == 1 { + if coldkeys_to_drain_to.len() == 0 as usize || coldkeys_to_drain_to.len() == 1 as usize { // Extend the wallet to drain to. - coldkeys_to_drain_to.push(new_coldkey); + coldkeys_to_drain_to.push(new_coldkey.clone()); // Push the change. - ColdkeysToDrainTo::::insert( current_coldkey, drain_wallets ); + ColdkeysToDrainTo::::insert( current_coldkey, coldkeys_to_drain_to.clone() ); + } else { + return Err(Error::::ColdkeyIsInArbitration.into()); } // If this is the first time we have seen this key we will put the drain period to be in 1 week. - if coldkeys_to_drain_to.len() == 0 { + if coldkeys_to_drain_to.len() == 0 as usize { // Get the current block. let current_block: u64 = Self::get_current_block_as_u64(); @@ -926,8 +928,8 @@ impl Pallet { // Add this new coldkey to these coldkeys // Sanity Check. - if !next_period_coldkeys_to_drain.contains(&coldkey_i) { - next_period_coldkeys_to_drain.push(coldkey_i); + if !next_period_coldkeys_to_drain.contains(new_coldkey) { + next_period_coldkeys_to_drain.push(new_coldkey.clone()); } // Set the new coldkeys to drain here. @@ -953,22 +955,24 @@ impl Pallet { // 11. ColdToBeDrained( C1 ) returns [ DestD ] and is Drained // 12. len([ DestD ]) == 1, call_drain( C1, DestD ) - pub fn drain_all_pending_coldkeys() { + pub fn drain_all_pending_coldkeys() -> Weight { + let mut weight = frame_support::weights::Weight::from_parts(0, 0); // Get the block number let current_block: u64 = Self::get_current_block_as_u64(); // Get the coldkeys to drain here. - let mut source_coldkeys: Vec = ColdkeysToDrainOnBlock::::get( current_block ); + let source_coldkeys: Vec = ColdkeysToDrainOnBlock::::get( current_block ); ColdkeysToDrainOnBlock::::remove( current_block ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // Iterate over all keys in Drain and call drain_to_pending_coldkeys for each for coldkey_i in source_coldkeys.iter() { // Get the wallets to drain to for this coldkey. - if !ColdkeysToDrainTo::::contains_key( &coldkey_i ) { continue } // Sanity Check. let destinations_coldkeys: Vec = ColdkeysToDrainTo::::get( coldkey_i ); ColdkeysToDrainTo::::remove( &coldkey_i ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // If the wallets to drain is > 1, we extend the period. if destinations_coldkeys.len() > 1 { @@ -978,60 +982,75 @@ impl Pallet { // Get the coldkeys to drain at the next arbitrage period. let mut next_period_coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( next_arbitrage_period ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); // Add this new coldkey to these coldkeys // Sanity Check. - if !next_period_coldkeys_to_drain.contains(&coldkey_i) { - next_period_coldkeys_to_drain.push(coldkey_i); + if !next_period_coldkeys_to_drain.contains(coldkey_i) { + next_period_coldkeys_to_drain.push(coldkey_i.clone()); } // Set the new coldkeys to drain here. ColdkeysToDrainOnBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_drain ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1)); } else if destinations_coldkeys.len() == 1 { // ONLY 1 wallet: Get the wallet to drain to. - let wallet_to_drain_to: T::AccountId = wallets_to_drain[0]; + let wallet_to_drain_to = &destinations_coldkeys[0]; // Perform the drain. - Self::drain_from_coldkeyA_to_coldkey_B( &coldkey_i, &wallet_to_drain_to ); + weight = weight.saturating_add( + Self::drain_from_coldkey_a_to_coldkey_b( &coldkey_i, wallet_to_drain_to ) + ); } } + + weight } - pub fn drain_from_coldkeyA_to_coldkey_B( coldkeyA: &T::AccountId, coldkeyB: &T::AccountId ) { + pub fn drain_from_coldkey_a_to_coldkey_b( coldkey_a: &T::AccountId, coldkey_b: &T::AccountId ) -> Weight { + let mut weight = frame_support::weights::Weight::from_parts(0, 0); - // Get the hotkeys associated with coldkeyA. - let coldkeyA_hotkeys: Vec = StakingHotkeys::::get( &coldkeyA ); + // Get the hotkeys associated with coldkey_a. + let coldkey_a_hotkeys: Vec = StakingHotkeys::::get( &coldkey_a ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); // Iterate over all the hotkeys associated with this coldkey - for hotkey_i in coldkeyA_hotkeys.iter() { + for hotkey_i in coldkey_a_hotkeys.iter() { - // Get the current stake from coldkeyA to hotkey_i. - let all_current_stake_i: u64 = Self::get_stake_for_coldkey_and_hotkey( &coldkeyA, &hotkey_i ); + // Get the current stake from coldkey_a to hotkey_i. + let all_current_stake_i: u64 = Self::get_stake_for_coldkey_and_hotkey( &coldkey_a, &hotkey_i ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); // We remove the balance from the hotkey acount equal to all of it. - Self::decrease_stake_on_coldkey_hotkey_account( &coldkeyA, &hotkey_i, all_current_stake_i ); + Self::decrease_stake_on_coldkey_hotkey_account( &coldkey_a, &hotkey_i, all_current_stake_i ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 4)); // We add the balance to the coldkey. If the above fails we will not credit this coldkey. - Self::add_balance_to_coldkey_account( &coldkeyA, all_current_stake_i ); + Self::add_balance_to_coldkey_account( &coldkey_a, all_current_stake_i ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); } // Get the total balance here. - let total_balance = Self::get_coldkey_balance( &coldkeyA ); + let total_balance = Self::get_coldkey_balance( &coldkey_a ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); if !total_balance.is_zero() { - // Attempt to transfer the entire total balance to coldkeyB. - T::Currency::transfer( - ¤t_coldkey, - &new_coldkey, + // Attempt to transfer the entire total balance to coldkey_b. + let _ = T::Currency::transfer( + coldkey_a, + coldkey_b, total_balance, Preservation::Expendable, ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); } + + weight } pub fn coldkey_is_locked( coldkey: &T::AccountId ) -> bool { - ColdkeyToDrainTo::::contains_key( &coldkey ) + ColdkeysToDrainTo::::contains_key( coldkey ) } } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 4030a0f5c..b2875b390 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3159,8 +3159,8 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - new_coldkey + ¤t_coldkey, + &new_coldkey )); // Check that the stake has been removed @@ -3188,8 +3188,8 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { assert_noop!( SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - current_coldkey + ¤t_coldkey, + ¤t_coldkey ), Error::::SameColdkey ); @@ -3230,8 +3230,8 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { // Try to unstake and transfer let result = SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - new_coldkey, + ¤t_coldkey, + &new_coldkey, ); // Print the result @@ -3302,8 +3302,8 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { // Perform unstake and transfer assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - new_coldkey + ¤t_coldkey, + &new_coldkey )); // Print final balances @@ -3350,8 +3350,8 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { )); assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - new_coldkey + ¤t_coldkey, + &new_coldkey )); // Check that all stake has been removed @@ -3397,8 +3397,8 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple 300 )); assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( - current_coldkey, - new_coldkey + ¤t_coldkey, + &new_coldkey )); assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 0); assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 0); From 7149b5bb8edcd476041283341e1eba9eafde63dd Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 5 Jul 2024 19:53:12 -0400 Subject: [PATCH 084/185] Make tests build for draining coldkeys --- pallets/subtensor/src/staking.rs | 12 +++++++++--- pallets/subtensor/tests/staking.rs | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 11bcbf564..9e8a922b6 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -893,6 +893,11 @@ impl Pallet { new_coldkey: &T::AccountId, ) -> DispatchResult { + ensure!( + current_coldkey != new_coldkey, + Error::::SameColdkey + ); + // Get the current wallets to drain to. let mut coldkeys_to_drain_to: Vec = ColdkeysToDrainTo::::get( current_coldkey ); @@ -902,8 +907,9 @@ impl Pallet { Error::::DuplicateColdkey ); - // Add the wallet to the drain wallets. - if coldkeys_to_drain_to.len() == 0 as usize || coldkeys_to_drain_to.len() == 1 as usize { + // Add the wallet to the drain wallets. + let initial_coldkey_count = coldkeys_to_drain_to.len(); + if initial_coldkey_count == 0 as usize || initial_coldkey_count == 1 as usize { // Extend the wallet to drain to. coldkeys_to_drain_to.push(new_coldkey.clone()); @@ -915,7 +921,7 @@ impl Pallet { } // If this is the first time we have seen this key we will put the drain period to be in 1 week. - if coldkeys_to_drain_to.len() == 0 as usize { + if initial_coldkey_count == 0 as usize { // Get the current block. let current_block: u64 = Self::get_current_block_as_u64(); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index b2875b390..4c71e6d60 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3163,6 +3163,12 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { &new_coldkey )); + // Make 7200 * 7 blocks pass + run_to_block(7200 * 7 + 1); + + // Run unstaking + SubtensorModule::drain_all_pending_coldkeys(); + // Check that the stake has been removed assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 0); From b76408a882d4eb25477338e5416042c49d477c04 Mon Sep 17 00:00:00 2001 From: const Date: Fri, 5 Jul 2024 19:15:24 -0500 Subject: [PATCH 085/185] merge --- pallets/subtensor/src/swap.rs | 44 ++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 137956dc3..22d86985e 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -121,12 +121,33 @@ impl Pallet { let mut weight = T::DbWeight::get().reads(2); - // Check if the new coldkey is already associated with any hotkeys + // Check that the coldkey is a new key (does not exist elsewhere.) ensure!( !Self::coldkey_has_associated_hotkeys(new_coldkey), Error::::ColdKeyAlreadyAssociated ); + // Check that the new coldkey is not a hotkey. + ensure!( + !Self::hotkey_account_exists(new_coldkey), + Error::::ColdKeyAlreadyAssociated + ); + + // Actually do the swap. + Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); + + Self::set_last_tx_block(new_coldkey, block); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + Self::deposit_event(Event::ColdkeySwapped { + old_coldkey: old_coldkey.clone(), + new_coldkey: new_coldkey.clone(), + }); + + Ok(Some(weight).into()) + } + + pub fn perform_swap_coldkey(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, weight: &mut Weight) { + // Get the current block. let block: u64 = Self::get_current_block_as_u64(); // Swap coldkey references in storage maps @@ -149,17 +170,18 @@ impl Pallet { Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); } - Self::set_last_tx_block(new_coldkey, block); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - Self::deposit_event(Event::ColdkeySwapped { - old_coldkey: old_coldkey.clone(), - new_coldkey: new_coldkey.clone(), - }); - - Ok(Some(weight).into()) + // Swap the coldkey. + let total_balance = Self::get_coldkey_balance( &old_coldkey ); + if !total_balance.is_zero() { + // Attempt to transfer the entire total balance to coldkeyB. + T::Currency::transfer( + &old_coldkey, + &new_coldkey, + total_balance, + Preservation::Expendable, + ); + } } - /// Retrieves the network membership status for a given hotkey. /// /// # Arguments From a499116bc3e6db2a955669550caf205834625b98 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Sat, 6 Jul 2024 10:03:46 -0400 Subject: [PATCH 086/185] Fix coldkey drain --- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/staking.rs | 15 +++++++-- pallets/subtensor/tests/staking.rs | 52 ++++++++++++++++++++++++------ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d8cdc81dc..1a10cbc49 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2060,7 +2060,7 @@ pub mod pallet { new_coldkey: T::AccountId, ) -> DispatchResult { let current_coldkey = ensure_signed(origin)?; - Self::do_unstake_all_and_transfer_to_new_coldkey(¤t_coldkey, &new_coldkey) + Self::schedule_unstake_all_and_transfer_to_new_coldkey(¤t_coldkey, &new_coldkey) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 9e8a922b6..b2b35b107 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -888,7 +888,7 @@ impl Pallet { /// Emits an `AllBalanceUnstakedAndTransferredToNewColdkey` event upon successful execution. /// Emits a `PartialBalanceTransferredToNewColdkey` event if only a partial transfer is successful. /// - pub fn do_unstake_all_and_transfer_to_new_coldkey( + pub fn schedule_unstake_all_and_transfer_to_new_coldkey( current_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResult { @@ -934,8 +934,8 @@ impl Pallet { // Add this new coldkey to these coldkeys // Sanity Check. - if !next_period_coldkeys_to_drain.contains(new_coldkey) { - next_period_coldkeys_to_drain.push(new_coldkey.clone()); + if !next_period_coldkeys_to_drain.contains(current_coldkey) { + next_period_coldkeys_to_drain.push(current_coldkey.clone()); } // Set the new coldkeys to drain here. @@ -1053,6 +1053,15 @@ impl Pallet { weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); } + // Emit event + Self::deposit_event( + Event::AllBalanceUnstakedAndTransferredToNewColdkey { + current_coldkey: coldkey_a.clone(), + new_coldkey: coldkey_b.clone(), + total_balance + } + ); + weight } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 4c71e6d60..ff15bf479 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3158,13 +3158,26 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { new_test_ext(1).execute_with(|| { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); - assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( ¤t_coldkey, &new_coldkey )); + // Check that ColdkeysToDrainTo is populated correctly + assert_eq!( + pallet_subtensor::ColdkeysToDrainTo::::get(current_coldkey), + vec![new_coldkey] + ); + + // Check that drain block is set correctly + let drain_block: u64 = 7200 * 7 + 1; + assert_eq!( + pallet_subtensor::ColdkeysToDrainOnBlock::::get(drain_block), + vec![current_coldkey] + ); + // Make 7200 * 7 blocks pass - run_to_block(7200 * 7 + 1); + run_to_block(drain_block); // Run unstaking SubtensorModule::drain_all_pending_coldkeys(); @@ -3193,7 +3206,7 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { let (current_coldkey, _hotkey, _) = setup_test_environment(); assert_noop!( - SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( ¤t_coldkey, ¤t_coldkey ), @@ -3235,7 +3248,7 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Try to unstake and transfer - let result = SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + let result = SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( ¤t_coldkey, &new_coldkey, ); @@ -3260,9 +3273,6 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { Balances::total_balance(&new_coldkey) ); - // Assert the expected error - assert_noop!(result, Error::::NoBalanceToTransfer); - // Verify that no balance was transferred assert_eq!(Balances::total_balance(¤t_coldkey), 0); assert_eq!(Balances::total_balance(&hotkey), 0); @@ -3307,11 +3317,18 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Perform unstake and transfer - assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( ¤t_coldkey, &new_coldkey )); + // Make 7200 * 7 blocks pass + let drain_block: u64 = 7200 * 7 + 1; + run_to_block(drain_block); + + // Run unstaking + SubtensorModule::drain_all_pending_coldkeys(); + // Print final balances log::info!( "Final current_coldkey balance: {:?}", @@ -3355,11 +3372,18 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { 300 )); - assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( ¤t_coldkey, &new_coldkey )); + // Make 7200 * 7 blocks pass + let drain_block: u64 = 7200 * 7 + 1; + run_to_block(drain_block); + + // Run unstaking + SubtensorModule::drain_all_pending_coldkeys(); + // Check that all stake has been removed assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 0); @@ -3402,10 +3426,18 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple hotkey2, 300 )); - assert_ok!(SubtensorModule::do_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( ¤t_coldkey, &new_coldkey )); + + // Make 7200 * 7 blocks pass + let drain_block: u64 = 7200 * 7 + 1; + run_to_block(drain_block); + + // Run unstaking + SubtensorModule::drain_all_pending_coldkeys(); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 0); assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 0); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); From c5aef40317e87760fe0b3f63bb6dcee1f71e3449 Mon Sep 17 00:00:00 2001 From: const Date: Sat, 6 Jul 2024 16:31:10 -0500 Subject: [PATCH 087/185] check --- pallets/subtensor/src/errors.rs | 2 + pallets/subtensor/src/lib.rs | 18 +- pallets/subtensor/src/registration.rs | 2 +- pallets/subtensor/src/root.rs | 10 +- pallets/subtensor/src/staking.rs | 272 ++++++-------------------- pallets/subtensor/src/swap.rs | 176 +++++++++++++++-- pallets/subtensor/tests/staking.rs | 6 +- 7 files changed, 238 insertions(+), 248 deletions(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index ed0afb184..cc1aa2055 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -150,5 +150,7 @@ mod errors { ColdkeyIsInArbitration, /// The new coldkey is already registered for the drain DuplicateColdkey, + /// Error thrown on a coldkey swap. + SwapError, } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 1a10cbc49..f3da9adbc 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -385,9 +385,9 @@ pub mod pallet { #[pallet::type_value] pub fn EmptyAccounts() -> Vec { vec![] } #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. - pub type ColdkeysToDrainTo = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery, EmptyAccounts>; + pub type ColdkeySwapDestinations = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery, EmptyAccounts>; #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. - pub type ColdkeysToDrainOnBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; + pub type ColdkeysToArbitrateAtBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it @@ -1312,7 +1312,10 @@ pub mod pallet { // - The number of the block we are initializing. fn on_initialize(_block_number: BlockNumberFor) -> Weight { // Unstake all and transfer pending coldkeys - let drain_weight = Self::drain_all_pending_coldkeys(); + let swap_weight = match Self::arbitrate_coldkeys_this_block() { + Ok(weight) => weight, + Err(_) => Weight::from_parts(0, 0), + }; let block_step_result = Self::block_step(); match block_step_result { @@ -1322,7 +1325,7 @@ pub mod pallet { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) - .saturating_add(drain_weight) + .saturating_add(swap_weight) } Err(e) => { // --- If the block step was unsuccessful, return the weight anyway. @@ -1330,7 +1333,7 @@ pub mod pallet { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) - .saturating_add(drain_weight) + .saturating_add(swap_weight) } } } @@ -2055,12 +2058,11 @@ pub mod pallet { #[pallet::weight((Weight::from_parts(1_940_000_000, 0) .saturating_add(T::DbWeight::get().reads(272)) .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - pub fn unstake_all_and_transfer_to_new_coldkey( + pub fn arbitrated_coldkey_swap( origin: OriginFor, new_coldkey: T::AccountId, ) -> DispatchResult { - let current_coldkey = ensure_signed(origin)?; - Self::schedule_unstake_all_and_transfer_to_new_coldkey(¤t_coldkey, &new_coldkey) + Self::do_arbitrated_coldkey_swap( origin, &new_coldkey ) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index bd162a5cf..3a4af23f2 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -41,7 +41,7 @@ impl Pallet { ) -> DispatchResult { // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_registration( coldkey:{:?} netuid:{:?} hotkey:{:?} )", coldkey, diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index e65524909..2db11e302 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -483,7 +483,7 @@ impl Pallet { // --- 1. Ensure that the call originates from a signed source and retrieve the caller's account ID (coldkey). let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_root_register( coldkey: {:?}, hotkey: {:?} )", coldkey, @@ -624,7 +624,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // Check the caller's signature. This is the coldkey of a registered account. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_set_root_weights( origin:{:?} netuid:{:?}, uids:{:?}, values:{:?})", coldkey, @@ -746,7 +746,7 @@ impl Pallet { ) -> DispatchResultWithPostInfo { // --- 1. Ensure that the caller has signed with their coldkey. let coldkey = ensure_signed(origin.clone())?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure that the calling coldkey owns the associated hotkey. ensure!( @@ -800,7 +800,7 @@ impl Pallet { pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // --- 1. Rate limit for network registrations. let current_block = Self::get_current_block_as_u64(); @@ -889,7 +889,7 @@ impl Pallet { pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { // --- 1. Ensure the function caller is a signed user. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure this subnet exists. ensure!( diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index b2b35b107..b2ffe3da0 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -7,10 +7,8 @@ use frame_support::{ Fortitude, Precision, Preservation, }, Imbalance, - }, weights::Weight, + } }; -use num_traits::Zero; -use sp_core::Get; impl Pallet { /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. @@ -46,7 +44,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signuture. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); log::info!( "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, @@ -136,7 +134,7 @@ impl Pallet { hotkey, take ); - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -209,7 +207,7 @@ impl Pallet { hotkey, take ); - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -295,7 +293,7 @@ impl Pallet { hotkey, stake_to_be_added ); - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // Ensure the callers coldkey has enough stake to perform the transaction. ensure!( @@ -408,7 +406,7 @@ impl Pallet { hotkey, stake_to_be_removed ); - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); // Ensure that the hotkey account exists this is only possible through registration. ensure!( @@ -856,216 +854,56 @@ impl Pallet { } } - /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. - /// - /// This function performs the following operations: - /// 1. Verifies that the hotkey exists and is owned by the current coldkey. - /// 2. Ensures that the new coldkey is different from the current one. - /// 3. Unstakes all balance if there's any stake. - /// 4. Transfers the entire balance of the hotkey to the new coldkey. - /// 5. Verifies the success of the transfer and handles partial transfers if necessary. - /// - /// # Arguments - /// - /// * `current_coldkey` - The AccountId of the current coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey to receive the unstaked tokens. - /// - /// # Returns - /// - /// Returns a `DispatchResult` indicating success or failure of the operation. - /// - /// # Errors - /// - /// This function will return an error if: - /// * The hotkey account does not exist. - /// * The current coldkey does not own the hotkey. - /// * The new coldkey is the same as the current coldkey. - /// * There is no balance to transfer. - /// * The transfer fails or is only partially successful. - /// - /// # Events - /// - /// Emits an `AllBalanceUnstakedAndTransferredToNewColdkey` event upon successful execution. - /// Emits a `PartialBalanceTransferredToNewColdkey` event if only a partial transfer is successful. - /// - pub fn schedule_unstake_all_and_transfer_to_new_coldkey( - current_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) -> DispatchResult { - - ensure!( - current_coldkey != new_coldkey, - Error::::SameColdkey - ); - - // Get the current wallets to drain to. - let mut coldkeys_to_drain_to: Vec = ColdkeysToDrainTo::::get( current_coldkey ); - - // Check if the new coldkey is already in the drain wallets list - ensure!( - !coldkeys_to_drain_to.contains( new_coldkey ), - Error::::DuplicateColdkey - ); - // Add the wallet to the drain wallets. - let initial_coldkey_count = coldkeys_to_drain_to.len(); - if initial_coldkey_count == 0 as usize || initial_coldkey_count == 1 as usize { - - // Extend the wallet to drain to. - coldkeys_to_drain_to.push(new_coldkey.clone()); - - // Push the change. - ColdkeysToDrainTo::::insert( current_coldkey, coldkeys_to_drain_to.clone() ); - } else { - return Err(Error::::ColdkeyIsInArbitration.into()); - } - - // If this is the first time we have seen this key we will put the drain period to be in 1 week. - if initial_coldkey_count == 0 as usize { - - // Get the current block. - let current_block: u64 = Self::get_current_block_as_u64(); - - // Next arbitrage period - let next_arbitrage_period: u64 = current_block + 7200 * 7; - - // First time seeing this key lets push the drain moment to 1 week in the future. - let mut next_period_coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( next_arbitrage_period ); - - // Add this new coldkey to these coldkeys - // Sanity Check. - if !next_period_coldkeys_to_drain.contains(current_coldkey) { - next_period_coldkeys_to_drain.push(current_coldkey.clone()); - } - - // Set the new coldkeys to drain here. - ColdkeysToDrainOnBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_drain ); - } - - // Return true. - Ok(()) - } - - - // Chain opens, this is the only allowed transaction. - // 1. Someone calls drain with key C1, DestA, DestA is added to ColdToBeDrained and C1 is given a drain block in 7 days. - // 2. Someone else calls drain with key C1, DestB, DestB is added to ColdToBeDrained and C1 already has a drain block, not updated. - // 3. Someone calls drain key with C1, DestC. Dest C is not added to the ColdToBeDrained. No-op/ - // 4. 7200 * 7 blocks progress. - // 5. ColdkeysToDrainOnBlock( block ) returns [ C1 ] - // 6. ColdToBeDrained( C1 ) returns [ DestA, DestB ] and is Drained - // 7. len([ DestA, DestB ]) == 2, set the drain block to be 7 days in the future. - // 8. Someone calls drain with key C1, DestA, DestD is added to ColdToBeDrained and C1 is given a drain block in 7 days. - // 9. 7200 * 7 blocks progress. - // 10. ColdkeysToDrainOnBlock( block ) returns [ C1 ] - // 11. ColdToBeDrained( C1 ) returns [ DestD ] and is Drained - // 12. len([ DestD ]) == 1, call_drain( C1, DestD ) - - pub fn drain_all_pending_coldkeys() -> Weight { - let mut weight = frame_support::weights::Weight::from_parts(0, 0); - - // Get the block number - let current_block: u64 = Self::get_current_block_as_u64(); - - // Get the coldkeys to drain here. - let source_coldkeys: Vec = ColdkeysToDrainOnBlock::::get( current_block ); - ColdkeysToDrainOnBlock::::remove( current_block ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - // Iterate over all keys in Drain and call drain_to_pending_coldkeys for each - for coldkey_i in source_coldkeys.iter() { - - // Get the wallets to drain to for this coldkey. - let destinations_coldkeys: Vec = ColdkeysToDrainTo::::get( coldkey_i ); - ColdkeysToDrainTo::::remove( &coldkey_i ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - // If the wallets to drain is > 1, we extend the period. - if destinations_coldkeys.len() > 1 { - - // Next arbitrage period - let next_arbitrage_period: u64 = current_block + 7200 * 30; - - // Get the coldkeys to drain at the next arbitrage period. - let mut next_period_coldkeys_to_drain: Vec = ColdkeysToDrainOnBlock::::get( next_arbitrage_period ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - // Add this new coldkey to these coldkeys - // Sanity Check. - if !next_period_coldkeys_to_drain.contains(coldkey_i) { - next_period_coldkeys_to_drain.push(coldkey_i.clone()); - } - - // Set the new coldkeys to drain here. - ColdkeysToDrainOnBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_drain ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1)); - - } else if destinations_coldkeys.len() == 1 { - // ONLY 1 wallet: Get the wallet to drain to. - let wallet_to_drain_to = &destinations_coldkeys[0]; - - // Perform the drain. - weight = weight.saturating_add( - Self::drain_from_coldkey_a_to_coldkey_b( &coldkey_i, wallet_to_drain_to ) - ); - } - } - - weight - } - - - pub fn drain_from_coldkey_a_to_coldkey_b( coldkey_a: &T::AccountId, coldkey_b: &T::AccountId ) -> Weight { - let mut weight = frame_support::weights::Weight::from_parts(0, 0); - // Get the hotkeys associated with coldkey_a. - let coldkey_a_hotkeys: Vec = StakingHotkeys::::get( &coldkey_a ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + // pub fn x( coldkey_a: &T::AccountId, coldkey_b: &T::AccountId ) -> Weight { + // let mut weight = frame_support::weights::Weight::from_parts(0, 0); + + // // Get the hotkeys associated with coldkey_a. + // let coldkey_a_hotkeys: Vec = StakingHotkeys::::get( &coldkey_a ); + // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + + // // Iterate over all the hotkeys associated with this coldkey + // for hotkey_i in coldkey_a_hotkeys.iter() { + + // // Get the current stake from coldkey_a to hotkey_i. + // let all_current_stake_i: u64 = Self::get_stake_for_coldkey_and_hotkey( &coldkey_a, &hotkey_i ); + // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + + // // We remove the balance from the hotkey acount equal to all of it. + // Self::decrease_stake_on_coldkey_hotkey_account( &coldkey_a, &hotkey_i, all_current_stake_i ); + // weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 4)); + + // // We add the balance to the coldkey. If the above fails we will not credit this coldkey. + // Self::add_balance_to_coldkey_account( &coldkey_a, all_current_stake_i ); + // weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); + // } + + // // Get the total balance here. + // let total_balance = Self::get_coldkey_balance( &coldkey_a ); + // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + + // if !total_balance.is_zero() { + // // Attempt to transfer the entire total balance to coldkey_b. + // let _ = T::Currency::transfer( + // coldkey_a, + // coldkey_b, + // total_balance, + // Preservation::Expendable, + // ); + // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + // } + + // // Emit event + // Self::deposit_event( + // Event::AllBalanceUnstakedAndTransferredToNewColdkey { + // current_coldkey: coldkey_a.clone(), + // new_coldkey: coldkey_b.clone(), + // total_balance + // } + // ); + + // weight + // } - // Iterate over all the hotkeys associated with this coldkey - for hotkey_i in coldkey_a_hotkeys.iter() { - - // Get the current stake from coldkey_a to hotkey_i. - let all_current_stake_i: u64 = Self::get_stake_for_coldkey_and_hotkey( &coldkey_a, &hotkey_i ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - // We remove the balance from the hotkey acount equal to all of it. - Self::decrease_stake_on_coldkey_hotkey_account( &coldkey_a, &hotkey_i, all_current_stake_i ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 4)); - - // We add the balance to the coldkey. If the above fails we will not credit this coldkey. - Self::add_balance_to_coldkey_account( &coldkey_a, all_current_stake_i ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); - } - - // Get the total balance here. - let total_balance = Self::get_coldkey_balance( &coldkey_a ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - if !total_balance.is_zero() { - // Attempt to transfer the entire total balance to coldkey_b. - let _ = T::Currency::transfer( - coldkey_a, - coldkey_b, - total_balance, - Preservation::Expendable, - ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - } - - // Emit event - Self::deposit_event( - Event::AllBalanceUnstakedAndTransferredToNewColdkey { - current_coldkey: coldkey_a.clone(), - new_coldkey: coldkey_b.clone(), - total_balance - } - ); - - weight - } - - pub fn coldkey_is_locked( coldkey: &T::AccountId ) -> bool { - ColdkeysToDrainTo::::contains_key( coldkey ) - } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 22d86985e..400144540 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -1,6 +1,8 @@ use super::*; -use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; use sp_core::Get; +use frame_support::traits::fungible::Mutate; +use frame_support::traits::tokens::Preservation; +use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; impl Pallet { /// Swaps the hotkey of a coldkey account. @@ -28,7 +30,7 @@ impl Pallet { new_hotkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); let mut weight = T::DbWeight::get().reads(2); @@ -117,9 +119,9 @@ impl Pallet { new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey_performing_swap = ensure_signed(origin)?; - ensure!(!Self::coldkey_is_locked(&coldkey_performing_swap), Error::::ColdkeyIsInArbitration); + ensure!(!Self::coldkey_in_arbitration(&coldkey_performing_swap), Error::::ColdkeyIsInArbitration); - let mut weight = T::DbWeight::get().reads(2); + let mut weight: Weight = T::DbWeight::get().reads(2); // Check that the coldkey is a new key (does not exist elsewhere.) ensure!( @@ -133,9 +135,9 @@ impl Pallet { ); // Actually do the swap. - Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); + weight = weight.saturating_add(Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight).map_err(|_| Error::::SwapError)?); - Self::set_last_tx_block(new_coldkey, block); + Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64() ); weight.saturating_accrue(T::DbWeight::get().writes(1)); Self::deposit_event(Event::ColdkeySwapped { @@ -146,10 +148,149 @@ impl Pallet { Ok(Some(weight).into()) } - pub fn perform_swap_coldkey(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, weight: &mut Weight) { - // Get the current block. - let block: u64 = Self::get_current_block_as_u64(); + /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey over a delayed + /// to faciliate arbitration. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which must be signed by the old coldkey. + /// * `new_coldkey` - The account ID of the new coldkey. + /// + /// # Errors + /// + /// This function will return an error if: + /// - The caller is the same key as the new key or if the new key is already in the arbitration keys. + /// - There are already 2 keys in arbitration for this coldkey. + /// + // The coldkey is in arbitration state. + pub fn coldkey_in_arbitration( coldkey: &T::AccountId ) -> bool { ColdkeySwapDestinations::::contains_key( coldkey ) } // Helper + pub fn do_arbitrated_coldkey_swap( + origin: T::RuntimeOrigin, + new_coldkey: &T::AccountId, + ) -> DispatchResult { + + // Attain the calling coldkey from the origin. + let old_coldkey:T::AccountId = ensure_signed(origin)?; + + // Catch spurious swaps. + ensure!( + old_coldkey != *new_coldkey, + Error::::SameColdkey + ); + + // Get current destination coldkeys. + let mut destination_coldkeys: Vec = ColdkeySwapDestinations::::get( old_coldkey.clone() ); + + // Check if the new coldkey is already in the swap wallets list + ensure!( + !destination_coldkeys.contains( new_coldkey ), + Error::::DuplicateColdkey + ); + + // Add the wallet to the swap wallets. + let initial_destination_count = destination_coldkeys.len(); + + // If the destinations keys are empty or have size 1 then we will add the new coldkey to the list. + if initial_destination_count == 0 as usize || initial_destination_count == 1 as usize { + // Extend the wallet to swap to. + destination_coldkeys.push( new_coldkey.clone() ); + + // Push the change to the storage. + ColdkeySwapDestinations::::insert( old_coldkey.clone(), destination_coldkeys.clone() ); + + } else { + + // If the destinations len > 1 we dont add the new coldkey. + return Err(Error::::ColdkeyIsInArbitration.into()); + } + + // If this is the first time we have seen this key we will put the swap period to be in 7 days. + if initial_destination_count == 0 as usize { + + // Set the arbitration period to be 7 days from now. + let next_arbitration_period: u64 = Self::get_current_block_as_u64() + 7200 * 7; + + // First time seeing this key lets push the swap moment to 1 week in the future. + let mut next_period_coldkeys_to_swap: Vec = ColdkeysToArbitrateAtBlock::::get( next_arbitration_period ); + + // Add the old coldkey to the next period keys to swap. + // Sanity Check. + if !next_period_coldkeys_to_swap.contains( old_coldkey.clone() ) { + next_period_coldkeys_to_swap.push( old_coldkey.clone() ); + } + + // Set the new coldkeys to swap here. + ColdkeysToArbitrateAtBlock::::insert( next_arbitration_period, next_period_coldkeys_to_swap ); + } + + // Return true. + Ok(()) + } + + /// Arbitrates coldkeys that are scheduled to be swapped on this block. + /// + /// This function retrieves the list of coldkeys scheduled to be swapped on the current block, + /// and processes each coldkey by either extending the arbitration period or performing the swap + /// to the new coldkey. + /// + /// # Returns + /// + /// * `Weight` - The total weight consumed by the operation. + pub fn arbitrate_coldkeys_this_block() -> Result { + let mut weight = frame_support::weights::Weight::from_parts(0, 0); + + // Get the block number + let current_block: u64 = Self::get_current_block_as_u64(); + + // Get the coldkeys to swap here and then remove them. + let source_coldkeys: Vec = ColdkeysToArbitrateAtBlock::::get( current_block ); + ColdkeysToArbitrateAtBlock::::remove( current_block ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + // Iterate over all keys in swap and call perform_swap_coldkey for each + for coldkey_i in source_coldkeys.iter() { + + // Get the wallets to swap to for this coldkey. + let destinations_coldkeys: Vec = ColdkeySwapDestinations::::get( coldkey_i ); + ColdkeySwapDestinations::::remove( &coldkey_i ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + // If the wallets to swap is > 1, we extend the period. + if destinations_coldkeys.len() > 1 { + + // Next arbitrage period + let next_arbitrage_period: u64 = current_block + 7200 * 7; + + // Get the coldkeys to swap at the next arbitrage period. + let mut next_period_coldkeys_to_swap: Vec = ColdkeysToArbitrateAtBlock::::get( next_arbitrage_period ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); + // Add this new coldkey to these coldkeys + // Sanity Check. + if !next_period_coldkeys_to_swap.contains(coldkey_i) { + next_period_coldkeys_to_swap.push(coldkey_i.clone()); + } + + // Set the new coldkeys to swap here. + ColdkeysToArbitrateAtBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_swap ); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1)); + + } else if destinations_coldkeys.len() == 1 { + // ONLY 1 wallet: Get the wallet to swap to. + let new_coldkey = &destinations_coldkeys[0]; + + // Perform the swap. + if let Err(_) = Self::perform_swap_coldkey( &coldkey_i, new_coldkey ).map(|w| weight = weight.saturating_add(w)) { + return Err("Failed to perform coldkey swap"); + } + } + } + + Ok(weight) + } + + + pub fn perform_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId ) -> Result { // Swap coldkey references in storage maps // NOTE The order of these calls is important Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); @@ -166,22 +307,29 @@ impl Pallet { // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { - Self::kill_coldkey_account(old_coldkey, remaining_balance)?; + if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { + return Err(e); + } Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); } // Swap the coldkey. - let total_balance = Self::get_coldkey_balance( &old_coldkey ); - if !total_balance.is_zero() { + let total_balance:u64 = Self::get_coldkey_balance(&old_coldkey); + if total_balance > 0 { // Attempt to transfer the entire total balance to coldkeyB. - T::Currency::transfer( + if let Err(e) = T::Currency::transfer( &old_coldkey, &new_coldkey, total_balance, Preservation::Expendable, - ); + ) { + return Err(e); + } } + + Ok(*weight) } + /// Retrieves the network membership status for a given hotkey. /// /// # Arguments diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index ff15bf479..2d894b51d 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3163,16 +3163,16 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { &new_coldkey )); - // Check that ColdkeysToDrainTo is populated correctly + // Check that ColdkeySwapDestinations is populated correctly assert_eq!( - pallet_subtensor::ColdkeysToDrainTo::::get(current_coldkey), + pallet_subtensor::ColdkeySwapDestinations::::get(current_coldkey), vec![new_coldkey] ); // Check that drain block is set correctly let drain_block: u64 = 7200 * 7 + 1; assert_eq!( - pallet_subtensor::ColdkeysToDrainOnBlock::::get(drain_block), + pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block), vec![current_coldkey] ); From 8851879fd8f3c6beb86e44c698197ebcf7a4b58e Mon Sep 17 00:00:00 2001 From: const Date: Sat, 6 Jul 2024 16:36:31 -0500 Subject: [PATCH 088/185] passing here --- pallets/subtensor/src/swap.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 400144540..d92e48b4f 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -135,7 +135,7 @@ impl Pallet { ); // Actually do the swap. - weight = weight.saturating_add(Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight).map_err(|_| Error::::SwapError)?); + weight = weight.saturating_add(Self::perform_swap_coldkey( old_coldkey, new_coldkey ).map_err(|_| Error::::SwapError)?); Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64() ); weight.saturating_accrue(T::DbWeight::get().writes(1)); @@ -215,7 +215,7 @@ impl Pallet { // Add the old coldkey to the next period keys to swap. // Sanity Check. - if !next_period_coldkeys_to_swap.contains( old_coldkey.clone() ) { + if !next_period_coldkeys_to_swap.contains( &old_coldkey.clone() ) { next_period_coldkeys_to_swap.push( old_coldkey.clone() ); } @@ -291,6 +291,10 @@ impl Pallet { pub fn perform_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId ) -> Result { + + // Init the weight. + let mut weight = frame_support::weights::Weight::from_parts(0, 0); + // Swap coldkey references in storage maps // NOTE The order of these calls is important Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); @@ -308,7 +312,7 @@ impl Pallet { let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { - return Err(e); + return Err(e.into()); } Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); } @@ -323,11 +327,11 @@ impl Pallet { total_balance, Preservation::Expendable, ) { - return Err(e); + return Err(e.into()); } } - Ok(*weight) + Ok(weight) } /// Retrieves the network membership status for a given hotkey. From e7337af2b9115c882ea741f2fbe513aaf995830e Mon Sep 17 00:00:00 2001 From: const Date: Sat, 6 Jul 2024 17:51:45 -0500 Subject: [PATCH 089/185] fxi tests on arbitrated coldkey swap --- pallets/subtensor/src/lib.rs | 13 +-- pallets/subtensor/src/swap.rs | 97 +++++++---------------- pallets/subtensor/src/utils.rs | 7 ++ pallets/subtensor/tests/staking.rs | 123 ++++++++++++++--------------- pallets/subtensor/tests/swap.rs | 100 +++++++++++------------ 5 files changed, 153 insertions(+), 187 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f3da9adbc..3309b8fed 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -366,6 +366,8 @@ pub mod pallet { #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] // --- MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. pub type Delegates = StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; @@ -390,9 +392,6 @@ pub mod pallet { pub type ColdkeysToArbitrateAtBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; - #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it - pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - /// -- ITEM (switches liquid alpha on) #[pallet::type_value] pub fn DefaultLiquidAlpha() -> bool { @@ -1321,7 +1320,7 @@ pub mod pallet { match block_step_result { Ok(_) => { // --- If the block step was successful, return the weight. - log::info!("Successfully ran block step."); + log::debug!("Successfully ran block step."); Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) @@ -2058,11 +2057,13 @@ pub mod pallet { #[pallet::weight((Weight::from_parts(1_940_000_000, 0) .saturating_add(T::DbWeight::get().reads(272)) .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - pub fn arbitrated_coldkey_swap( + pub fn schedule_arbitrated_coldkey_swap( origin: OriginFor, new_coldkey: T::AccountId, ) -> DispatchResult { - Self::do_arbitrated_coldkey_swap( origin, &new_coldkey ) + // Attain the calling coldkey from the origin. + let old_coldkey:T::AccountId = ensure_signed(origin)?; + Self::do_schedule_arbitrated_coldkey_swap( &old_coldkey, &new_coldkey ) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index d92e48b4f..a0fec9cfc 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -164,17 +164,14 @@ impl Pallet { /// // The coldkey is in arbitration state. pub fn coldkey_in_arbitration( coldkey: &T::AccountId ) -> bool { ColdkeySwapDestinations::::contains_key( coldkey ) } // Helper - pub fn do_arbitrated_coldkey_swap( - origin: T::RuntimeOrigin, + pub fn do_schedule_arbitrated_coldkey_swap( + old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResult { - // Attain the calling coldkey from the origin. - let old_coldkey:T::AccountId = ensure_signed(origin)?; - // Catch spurious swaps. ensure!( - old_coldkey != *new_coldkey, + *old_coldkey != *new_coldkey, Error::::SameColdkey ); @@ -241,6 +238,7 @@ impl Pallet { // Get the block number let current_block: u64 = Self::get_current_block_as_u64(); + log::debug!("Arbitrating coldkeys for block: {:?}", current_block); // Get the coldkeys to swap here and then remove them. let source_coldkeys: Vec = ColdkeysToArbitrateAtBlock::::get( current_block ); @@ -292,6 +290,7 @@ impl Pallet { pub fn perform_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId ) -> Result { + log::info!("Performing swap for coldkey: {:?} to {:?}", old_coldkey, new_coldkey); // Init the weight. let mut weight = frame_support::weights::Weight::from_parts(0, 0); @@ -299,14 +298,12 @@ impl Pallet { // NOTE The order of these calls is important Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); - Self::swap_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); Self::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( old_coldkey, new_coldkey, &mut weight, ); Self::swap_subnet_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); - Self::swap_owned_for_coldkey(old_coldkey, new_coldkey, &mut weight); // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); @@ -453,9 +450,13 @@ impl Pallet { let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); if !staking_hotkeys.contains(new_hotkey) { staking_hotkeys.push(new_hotkey.clone()); - StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); writes = writes.saturating_add(1u64); // One write for insert } + if let Some(pos) = staking_hotkeys.iter().position(|x| x == old_hotkey) { + staking_hotkeys.remove(pos); + writes = writes.saturating_add(1u64); // One write for remove + } + StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); } // Clear the prefix for the old hotkey after transferring all stakes @@ -700,45 +701,28 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - // Find all hotkeys for this coldkey - let hotkeys = OwnedHotkeys::::get(old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); - for hotkey in hotkeys.iter() { - let stake = Stake::::get(&hotkey, old_coldkey); - Stake::::remove(&hotkey, old_coldkey); - Stake::::insert(&hotkey, new_coldkey, stake); - - // Update StakingHotkeys map - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - StakingHotkeys::::insert(new_coldkey.clone(), staking_hotkeys); + // Swap the owners. + let old_owned_hotkeys = OwnedHotkeys::::get(old_coldkey); + for owned_key in old_owned_hotkeys.iter() { + Owner::::insert(owned_key, new_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); } - } - - /// Swaps the owner of all hotkeys from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Updates the owner of all hotkeys associated with the old coldkey to the new coldkey. - /// * Updates the transaction weight. - pub fn swap_owner_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - let hotkeys = OwnedHotkeys::::get(old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); - for hotkey in hotkeys.iter() { - Owner::::insert(&hotkey, new_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + OwnedHotkeys::::remove( old_coldkey.clone() ); + OwnedHotkeys::::insert( new_coldkey.clone(), old_owned_hotkeys ); + + // Swap all the keys the coldkey is staking too. + let staking_hotkeys = StakingHotkeys::::get( old_coldkey ); + StakingHotkeys::::remove( old_coldkey.clone() ); + for hotkey in staking_hotkeys.iter() { + // Remove the previous stake and re-insert it. + let stake = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + Stake::::insert(hotkey, new_coldkey, stake); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); } + // Add the new staking keys value. + StakingHotkeys::::insert( new_coldkey.clone(), staking_hotkeys.clone() ); } /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. @@ -779,7 +763,7 @@ impl Pallet { /// /// * `bool` - True if the coldkey has any associated hotkeys, false otherwise. pub fn coldkey_has_associated_hotkeys(coldkey: &T::AccountId) -> bool { - Owner::::iter().any(|(_, owner)| owner == *coldkey) + StakingHotkeys::::get(coldkey).len() > 0 } /// Swaps the subnet owner from the old coldkey to the new coldkey for all networks where the old coldkey is the owner. @@ -809,27 +793,4 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } - /// Swaps the owned hotkeys for the coldkey - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Updates the subnet owner to the new coldkey for all networks where the old coldkey was the owner. - /// * Updates the transaction weight. - pub fn swap_owned_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - // Update OwnedHotkeys map with new coldkey - let hotkeys = OwnedHotkeys::::get(old_coldkey); - OwnedHotkeys::::remove(old_coldkey); - OwnedHotkeys::::insert(new_coldkey, hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); - } } diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index faaaecebb..94f18dfbf 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -616,6 +616,13 @@ impl Pallet { Self::deposit_event(Event::SubnetOwnerCutSet(subnet_owner_cut)); } + pub fn get_owned_hotkeys(coldkey: &T::AccountId) -> Vec { + OwnedHotkeys::::get(coldkey) + } + pub fn get_all_staked_hotkeys(coldkey: &T::AccountId) -> Vec { + StakingHotkeys::::get(coldkey) + } + pub fn set_total_issuance(total_issuance: u64) { TotalIssuance::::put(total_issuance); } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 2d894b51d..eaef29474 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3153,12 +3153,13 @@ fn setup_test_environment() -> (AccountId, AccountId, AccountId) { (current_coldkey, hotkey, new_coldkey) } +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_success --exact --nocapture #[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { +fn test_arbitrated_coldkey_swap_success() { new_test_ext(1).execute_with(|| { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); - assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3175,38 +3176,36 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_success() { pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block), vec![current_coldkey] ); + log::info!("Drain block set correctly: {:?}", drain_block); + log::info!("Drain block {:?}", pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block)); // Make 7200 * 7 blocks pass run_to_block(drain_block); // Run unstaking - SubtensorModule::drain_all_pending_coldkeys(); + SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); + log::info!("Arbitrated coldkeys for block: {:?}", SubtensorModule::get_current_block_as_u64()); - // Check that the stake has been removed - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 0); + // Check the hotkey stake. + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 500); + + // Get the owner of the hotkey now new key. + assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), new_coldkey); // Check that the balance has been transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); - - // Check that the appropriate event was emitted - System::assert_last_event( - Event::AllBalanceUnstakedAndTransferredToNewColdkey { - current_coldkey, - new_coldkey, - total_balance: 1000, - } - .into(), - ); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 500); // The new key as the 500 + }); } +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_same_coldkey --exact --nocapture #[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { +fn test_arbitrated_coldkey_swap_same_coldkey() { new_test_ext(1).execute_with(|| { let (current_coldkey, _hotkey, _) = setup_test_environment(); assert_noop!( - SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( + SubtensorModule::do_schedule_arbitrated_coldkey_swap( ¤t_coldkey, ¤t_coldkey ), @@ -3215,8 +3214,9 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_same_coldkey() { }); } +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_no_balance --exact --nocapture #[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { +fn test_arbitrated_coldkey_swap_no_balance() { new_test_ext(1).execute_with(|| { // Create accounts manually let current_coldkey: AccountId = U256::from(1); @@ -3248,14 +3248,14 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Try to unstake and transfer - let result = SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( + let result = SubtensorModule::do_schedule_arbitrated_coldkey_swap( ¤t_coldkey, &new_coldkey, ); // Print the result log::info!( - "Result of do_unstake_all_and_transfer_to_new_coldkey: {:?}", + "Result of arbitrated_coldkey_swap: {:?}", result ); @@ -3280,8 +3280,10 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_no_balance() { }); } +// To run this test, use the following command: +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_with_no_stake --exact --nocapture #[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { +fn test_arbitrated_coldkey_swap_with_no_stake() { new_test_ext(1).execute_with(|| { // Create accounts manually let current_coldkey: AccountId = U256::from(1); @@ -3317,7 +3319,7 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Perform unstake and transfer - assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3327,7 +3329,7 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { run_to_block(drain_block); // Run unstaking - SubtensorModule::drain_all_pending_coldkeys(); + SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); // Print final balances log::info!( @@ -3347,19 +3349,12 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_no_stake() { assert_eq!(Balances::total_balance(&new_coldkey), initial_balance); assert_eq!(Balances::total_balance(¤t_coldkey), 0); - // Check that the appropriate event was emitted - System::assert_last_event( - Event::AllBalanceUnstakedAndTransferredToNewColdkey { - current_coldkey, - new_coldkey, - total_balance: initial_balance, - } - .into(), - ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_with_multiple_stakes --exact --nocapture #[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { +fn test_arbitrated_coldkey_swap_with_multiple_stakes() { new_test_ext(1).execute_with(|| { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); @@ -3372,7 +3367,7 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { 300 )); - assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3382,28 +3377,26 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes() { run_to_block(drain_block); // Run unstaking - SubtensorModule::drain_all_pending_coldkeys(); + SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); // Check that all stake has been removed - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 0); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 800); + + // Owner has changed + assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), new_coldkey); // Check that the full balance has been transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); - - // Check that the appropriate event was emitted - System::assert_last_event( - Event::AllBalanceUnstakedAndTransferredToNewColdkey { - current_coldkey, - new_coldkey, - total_balance: 1000, - } - .into(), - ); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 200); + + // Check that the full balance has been transferred to the new coldkey + assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); + }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_with_multiple_stakes_multiple --exact --nocapture #[test] -fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple() { +fn test_arbitrated_coldkey_swap_with_multiple_stakes_multiple() { new_test_ext(1).execute_with(|| { // Register the neuron to a new network let netuid = 1; @@ -3426,7 +3419,7 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple hotkey2, 300 )); - assert_ok!(SubtensorModule::schedule_unstake_all_and_transfer_to_new_coldkey( + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3436,18 +3429,22 @@ fn test_do_unstake_all_and_transfer_to_new_coldkey_with_multiple_stakes_multiple run_to_block(drain_block); // Run unstaking - SubtensorModule::drain_all_pending_coldkeys(); - - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 0); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 0); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 1000); - System::assert_last_event( - Event::AllBalanceUnstakedAndTransferredToNewColdkey { - current_coldkey, - new_coldkey, - total_balance: 1000, - } - .into(), - ); + SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); + + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 500); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 200); + assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); + // Owner has changed + assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey0), new_coldkey); + assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey2), new_coldkey); + // Get owned keys + assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey0, hotkey2]); + assert_eq!(SubtensorModule::get_owned_hotkeys(¤t_coldkey), vec![]); + // Get all staked keys. + assert_eq!(SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![hotkey0, hotkey2]); + assert_eq!(SubtensorModule::get_all_staked_hotkeys(¤t_coldkey), vec![]); + // Check + }); } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index d527c44e8..09dec6d8c 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1169,6 +1169,7 @@ fn test_swap_total_coldkey_stake() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_swap_stake_for_coldkey --exact --nocapture #[test] fn test_swap_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1179,25 +1180,38 @@ fn test_swap_stake_for_coldkey() { let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; let mut weight = Weight::zero(); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount1 + stake_amount2 + 123456); + add_network(1, 13, 0); + register_ok_neuron(1, hotkey1, old_coldkey, 0); + register_ok_neuron(1, hotkey2, old_coldkey, 0); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey1, + stake_amount1 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey2, + stake_amount2 + )); - // Initialize Stake for old_coldkey - Stake::::insert(hotkey1, old_coldkey, stake_amount1); - Stake::::insert(hotkey2, old_coldkey, stake_amount2); - - // Initialize TotalHotkeyStake - TotalHotkeyStake::::insert(hotkey1, stake_amount1); - TotalHotkeyStake::::insert(hotkey2, stake_amount2); - - // Initialize TotalStake and TotalIssuance - TotalStake::::put(stake_amount1 + stake_amount2); - TotalIssuance::::put(stake_amount1 + stake_amount2); - - // Populate OwnedHotkeys map - OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + // Get owned keys + assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![hotkey1, hotkey2]); + assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![]); + // Get all staked keys. + assert_eq!(SubtensorModule::get_all_staked_hotkeys(&old_coldkey), vec![hotkey1, hotkey2]); + assert_eq!(SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![]); // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + // Get owned keys + assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey1, hotkey2]); + assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); + // Get all staked keys. + assert_eq!(SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![hotkey1, hotkey2]); + assert_eq!(SubtensorModule::get_all_staked_hotkeys(&old_coldkey), vec![]); + // Verify the swap assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); @@ -1207,45 +1221,9 @@ fn test_swap_stake_for_coldkey() { // Verify TotalHotkeyStake remains unchanged assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); - - // Verify TotalStake and TotalIssuance remain unchanged - assert_eq!(TotalStake::::get(), stake_amount1 + stake_amount2); - assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(5, 6); - assert_eq!(weight, expected_weight); }); } -#[test] -fn test_swap_owner_for_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - let mut weight = Weight::zero(); - - // Initialize Owner for old_coldkey - Owner::::insert(hotkey1, old_coldkey); - Owner::::insert(hotkey2, old_coldkey); - - // Initialize OwnedHotkeys map - OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - - // Perform the swap - SubtensorModule::swap_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // Verify the swap - assert_eq!(Owner::::get(hotkey1), new_coldkey); - assert_eq!(Owner::::get(hotkey2), new_coldkey); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} #[test] fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { @@ -1380,3 +1358,25 @@ fn test_coldkey_has_associated_hotkeys() { )); }); } + +// #[test] +// fn test_coldkey_arbitrated_sw() { +// new_test_ext(1).execute_with(|| { +// let coldkey = U256::from(1); +// let hotkey = U256::from(2); +// let netuid = 1u16; + +// // Setup initial state +// add_network(netuid, 13, 0); +// register_ok_neuron(netuid, hotkey, coldkey, 0); + +// // Check if coldkey has associated hotkeys +// assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); + +// // Check for a coldkey without associated hotkeys +// let unassociated_coldkey = U256::from(3); +// assert!(!SubtensorModule::coldkey_has_associated_hotkeys( +// &unassociated_coldkey +// )); +// }); +// } From cf4c8d593f9b5c72ca2510b63ad35e97728229ba Mon Sep 17 00:00:00 2001 From: const Date: Sat, 6 Jul 2024 18:52:05 -0500 Subject: [PATCH 090/185] add more test --- pallets/subtensor/src/lib.rs | 9 ++- pallets/subtensor/src/swap.rs | 58 +++++---------- pallets/subtensor/tests/staking.rs | 115 +++++++++++++++++------------ 3 files changed, 92 insertions(+), 90 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 3309b8fed..f698585b0 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -383,11 +383,16 @@ pub mod pallet { DefaultAccountTake, >; - /// Default value for hotkeys. - #[pallet::type_value] + #[pallet::type_value] /// Default value for hotkeys. pub fn EmptyAccounts() -> Vec { vec![] } + #[pallet::type_value]/// Default stake interval. + pub fn DefaultArbitrationPeriod() -> u64 { 7200 * 4 } + #[pallet::storage] // ---- StorageItem Global Used Work. + pub type ArbitrationPeriod =StorageValue<_, u64, ValueQuery, DefaultArbitrationPeriod>; #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. pub type ColdkeySwapDestinations = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery, EmptyAccounts>; + #[pallet::storage] // --- MAP ( cold ) --> u64 | Block when the coldkey will be arbitrated. + pub type ColdkeyArbitrationBlock = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. pub type ColdkeysToArbitrateAtBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index a0fec9cfc..a3893dde3 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -163,7 +163,7 @@ impl Pallet { /// - There are already 2 keys in arbitration for this coldkey. /// // The coldkey is in arbitration state. - pub fn coldkey_in_arbitration( coldkey: &T::AccountId ) -> bool { ColdkeySwapDestinations::::contains_key( coldkey ) } // Helper + pub fn coldkey_in_arbitration( coldkey: &T::AccountId ) -> bool { ColdkeyArbitrationBlock::::get(coldkey) > Self::get_current_block_as_u64() } pub fn do_schedule_arbitrated_coldkey_swap( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, @@ -184,40 +184,29 @@ impl Pallet { Error::::DuplicateColdkey ); - // Add the wallet to the swap wallets. - let initial_destination_count = destination_coldkeys.len(); - // If the destinations keys are empty or have size 1 then we will add the new coldkey to the list. - if initial_destination_count == 0 as usize || initial_destination_count == 1 as usize { - // Extend the wallet to swap to. + if destination_coldkeys.len() == 0 as usize || destination_coldkeys.len() == 1 as usize { + // Add this wallet to exist in the destination list. destination_coldkeys.push( new_coldkey.clone() ); - - // Push the change to the storage. ColdkeySwapDestinations::::insert( old_coldkey.clone(), destination_coldkeys.clone() ); - } else { - - // If the destinations len > 1 we dont add the new coldkey. return Err(Error::::ColdkeyIsInArbitration.into()); } - // If this is the first time we have seen this key we will put the swap period to be in 7 days. - if initial_destination_count == 0 as usize { - - // Set the arbitration period to be 7 days from now. - let next_arbitration_period: u64 = Self::get_current_block_as_u64() + 7200 * 7; + // It is the first time we have seen this key. + if destination_coldkeys.len() == 1 as usize { - // First time seeing this key lets push the swap moment to 1 week in the future. - let mut next_period_coldkeys_to_swap: Vec = ColdkeysToArbitrateAtBlock::::get( next_arbitration_period ); + // Set the arbitration block for this coldkey. + let arbitration_block: u64 = Self::get_current_block_as_u64() + ArbitrationPeriod::::get(); + ColdkeyArbitrationBlock::::insert( old_coldkey.clone(), arbitration_block ); - // Add the old coldkey to the next period keys to swap. - // Sanity Check. - if !next_period_coldkeys_to_swap.contains( &old_coldkey.clone() ) { - next_period_coldkeys_to_swap.push( old_coldkey.clone() ); + // Update the list of coldkeys arbitrate on this block. + let mut key_to_arbitrate_on_this_block: Vec = ColdkeysToArbitrateAtBlock::::get( arbitration_block ); + if !key_to_arbitrate_on_this_block.contains( &old_coldkey.clone() ) { + key_to_arbitrate_on_this_block.push( old_coldkey.clone() ); } + ColdkeysToArbitrateAtBlock::::insert( arbitration_block, key_to_arbitrate_on_this_block ); - // Set the new coldkeys to swap here. - ColdkeysToArbitrateAtBlock::::insert( next_arbitration_period, next_period_coldkeys_to_swap ); } // Return true. @@ -253,25 +242,12 @@ impl Pallet { ColdkeySwapDestinations::::remove( &coldkey_i ); weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - // If the wallets to swap is > 1, we extend the period. + // If the wallets to swap is > 1 we do nothing. if destinations_coldkeys.len() > 1 { - // Next arbitrage period - let next_arbitrage_period: u64 = current_block + 7200 * 7; - - // Get the coldkeys to swap at the next arbitrage period. - let mut next_period_coldkeys_to_swap: Vec = ColdkeysToArbitrateAtBlock::::get( next_arbitrage_period ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - // Add this new coldkey to these coldkeys - // Sanity Check. - if !next_period_coldkeys_to_swap.contains(coldkey_i) { - next_period_coldkeys_to_swap.push(coldkey_i.clone()); - } - - // Set the new coldkeys to swap here. - ColdkeysToArbitrateAtBlock::::insert( next_arbitrage_period, next_period_coldkeys_to_swap ); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1)); + // Update the arbitration period but we still have the same wallet to swap to. + let next_arbitrage_period: u64 = current_block + ArbitrationPeriod::::get(); + ColdkeyArbitrationBlock::::insert( coldkey_i.clone(), next_arbitrage_period ); } else if destinations_coldkeys.len() == 1 { // ONLY 1 wallet: Get the wallet to swap to. diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index eaef29474..9c87590ed 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3394,57 +3394,78 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_with_multiple_stakes_multiple --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_multiple_arbitrations --exact --nocapture #[test] -fn test_arbitrated_coldkey_swap_with_multiple_stakes_multiple() { +fn test_arbitrated_coldkey_swap_multiple_arbitrations() { new_test_ext(1).execute_with(|| { - // Register the neuron to a new network - let netuid = 1; - let hotkey0 = U256::from(1); - let hotkey2 = U256::from(2); - let current_coldkey = U256::from(3); - let new_coldkey = U256::from(4); - add_network(netuid, 0, 0); - register_ok_neuron(1, hotkey0, current_coldkey, 0); - register_ok_neuron(1, hotkey2, current_coldkey, 0); - SubtensorModule::set_target_stakes_per_interval(10); - SubtensorModule::add_balance_to_coldkey_account(¤t_coldkey, 1000); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(current_coldkey), - hotkey0, - 500 - )); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(current_coldkey), - hotkey2, - 300 - )); - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( - ¤t_coldkey, - &new_coldkey - )); - // Make 7200 * 7 blocks pass - let drain_block: u64 = 7200 * 7 + 1; - run_to_block(drain_block); + // Create coldkey with two choices. + let coldkey: AccountId = U256::from(1); + let new_coldkey1: AccountId = U256::from(2); + let new_coldkey2: AccountId = U256::from(3); + let hotkey: AccountId = U256::from(4); - // Run unstaking - SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); + // Setup network state. + add_network(1, 0, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + ArbitrationPeriod::::put(1); // One block arbitration period. + register_ok_neuron(1, hotkey, coldkey, 0); + + // Owner schedules a swap for themselves. + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( &coldkey, &new_coldkey1 )); + + // Attacker schedules the second swap for themselves. + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( &coldkey, &new_coldkey2 )); + + // Both keys are added in swap destinations. + assert_eq!( pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), vec![new_coldkey1, new_coldkey2] ); + + // Check that we are arbitrating next block. + assert_eq!( pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(SubtensorModule::get_current_block_as_u64() + 1), vec![coldkey] ); + + // Key is in arbitration. + assert!( SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + + // Arbitrate next block. + assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get( &coldkey ), SubtensorModule::get_current_block_as_u64() + 1 ); + + // Arbitrate. + step_block(1); + + // Both keys are removed and a new period begins. + assert_eq!( pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), vec![] ); + + // Arbitration has been pushed back but there are no keys to add to the list to arbitrate. + assert_eq!( pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(SubtensorModule::get_current_block_as_u64() + 1), vec![] ); + + // Key is in arbitration. + assert!( SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + + // Arbitrate next block. + assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get( &coldkey ), SubtensorModule::get_current_block_as_u64() + 1 ); + + // Arbitrate. + step_block(1); + + // Key is not in arbitration + assert!( !SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + + // Owner schedules a swap for themselves leys go back into arbitration. + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( &coldkey, &new_coldkey1 )); + + // Key goes back into arbitration. + assert!( SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + + // Arbitrate next block. + assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get( &coldkey ), SubtensorModule::get_current_block_as_u64() + 1 ); + + // Arbitrate. + step_block(1); + + // New key gets amount the other keys are empty. + assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey), 0); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey1), 1000); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 0); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 500); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 200); - assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); - // Owner has changed - assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey0), new_coldkey); - assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey2), new_coldkey); - // Get owned keys - assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey0, hotkey2]); - assert_eq!(SubtensorModule::get_owned_hotkeys(¤t_coldkey), vec![]); - // Get all staked keys. - assert_eq!(SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![hotkey0, hotkey2]); - assert_eq!(SubtensorModule::get_all_staked_hotkeys(¤t_coldkey), vec![]); - // Check - }); } From c5b78f80f6ca84086e147dc77f537791762437ef Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 05:25:01 +0400 Subject: [PATCH 091/185] chore: update comments, lints --- pallets/subtensor/src/lib.rs | 40 +++++-- pallets/subtensor/src/migration.rs | 9 +- pallets/subtensor/src/registration.rs | 5 +- pallets/subtensor/src/root.rs | 27 ++++- pallets/subtensor/src/staking.rs | 30 +++-- pallets/subtensor/src/swap.rs | 161 ++++++++++++++++---------- pallets/subtensor/tests/staking.rs | 100 +++++++++++----- pallets/subtensor/tests/swap.rs | 40 +++++-- 8 files changed, 276 insertions(+), 136 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f698585b0..3756fd98a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -367,7 +367,8 @@ pub mod pallet { pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it - pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + pub type StakingHotkeys = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] // --- MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. pub type Delegates = StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; @@ -383,19 +384,34 @@ pub mod pallet { DefaultAccountTake, >; - #[pallet::type_value] /// Default value for hotkeys. - pub fn EmptyAccounts() -> Vec { vec![] } - #[pallet::type_value]/// Default stake interval. - pub fn DefaultArbitrationPeriod() -> u64 { 7200 * 4 } + #[pallet::type_value] + /// Default value for hotkeys. + pub fn EmptyAccounts() -> Vec { + vec![] + } + #[pallet::type_value] + /// Default stake interval. + pub fn DefaultArbitrationPeriod() -> u64 { + 7200 * 4 + } #[pallet::storage] // ---- StorageItem Global Used Work. - pub type ArbitrationPeriod =StorageValue<_, u64, ValueQuery, DefaultArbitrationPeriod>; + pub type ArbitrationPeriod = + StorageValue<_, u64, ValueQuery, DefaultArbitrationPeriod>; #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. - pub type ColdkeySwapDestinations = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery, EmptyAccounts>; + pub type ColdkeySwapDestinations = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + Vec, + ValueQuery, + EmptyAccounts, + >; #[pallet::storage] // --- MAP ( cold ) --> u64 | Block when the coldkey will be arbitrated. - pub type ColdkeyArbitrationBlock = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; + pub type ColdkeyArbitrationBlock = + StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. - pub type ColdkeysToArbitrateAtBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; - + pub type ColdkeysToArbitrateAtBlock = + StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; /// -- ITEM (switches liquid alpha on) #[pallet::type_value] @@ -2067,8 +2083,8 @@ pub mod pallet { new_coldkey: T::AccountId, ) -> DispatchResult { // Attain the calling coldkey from the origin. - let old_coldkey:T::AccountId = ensure_signed(origin)?; - Self::do_schedule_arbitrated_coldkey_swap( &old_coldkey, &new_coldkey ) + let old_coldkey: T::AccountId = ensure_signed(origin)?; + Self::do_schedule_arbitrated_coldkey_swap(&old_coldkey, &new_coldkey) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 400030450..35b3c4260 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -565,23 +565,23 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { if stake > 0 { let mut hotkeys = StakingHotkeys::::get(&coldkey); storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage - + // Add the hotkey if it's not already in the vector if !hotkeys.contains(&hotkey) { hotkeys.push(hotkey); keys_touched = keys_touched.saturating_add(1); - + // Update longest hotkey vector info if longest_hotkey_vector < hotkeys.len() { longest_hotkey_vector = hotkeys.len(); longest_coldkey = Some(coldkey.clone()); } - + // Update the StakingHotkeys storage StakingHotkeys::::insert(&coldkey, hotkeys); storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage } - + // Accrue weight for reads and writes weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); } @@ -603,4 +603,3 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { Weight::zero() } } - diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 3a4af23f2..6b73f2fc3 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -41,7 +41,10 @@ impl Pallet { ) -> DispatchResult { // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); log::info!( "do_registration( coldkey:{:?} netuid:{:?} hotkey:{:?} )", coldkey, diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index 2db11e302..f39ae97e0 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -483,7 +483,10 @@ impl Pallet { // --- 1. Ensure that the call originates from a signed source and retrieve the caller's account ID (coldkey). let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); log::info!( "do_root_register( coldkey: {:?}, hotkey: {:?} )", coldkey, @@ -624,7 +627,10 @@ impl Pallet { ) -> dispatch::DispatchResult { // Check the caller's signature. This is the coldkey of a registered account. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); log::info!( "do_set_root_weights( origin:{:?} netuid:{:?}, uids:{:?}, values:{:?})", coldkey, @@ -746,7 +752,10 @@ impl Pallet { ) -> DispatchResultWithPostInfo { // --- 1. Ensure that the caller has signed with their coldkey. let coldkey = ensure_signed(origin.clone())?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); // --- 2. Ensure that the calling coldkey owns the associated hotkey. ensure!( @@ -800,8 +809,11 @@ impl Pallet { pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); - + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); + // --- 1. Rate limit for network registrations. let current_block = Self::get_current_block_as_u64(); let last_lock_block = Self::get_network_last_lock_block(); @@ -889,7 +901,10 @@ impl Pallet { pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { // --- 1. Ensure the function caller is a signed user. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); // --- 2. Ensure this subnet exists. ensure!( diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index b2ffe3da0..4ddca3fa1 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -7,7 +7,7 @@ use frame_support::{ Fortitude, Precision, Preservation, }, Imbalance, - } + }, }; impl Pallet { @@ -44,7 +44,10 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signuture. let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); log::info!( "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, @@ -134,7 +137,10 @@ impl Pallet { hotkey, take ); - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -207,7 +213,10 @@ impl Pallet { hotkey, take ); - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -293,7 +302,10 @@ impl Pallet { hotkey, stake_to_be_added ); - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); // Ensure the callers coldkey has enough stake to perform the transaction. ensure!( @@ -406,7 +418,10 @@ impl Pallet { hotkey, stake_to_be_removed ); - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); // Ensure that the hotkey account exists this is only possible through registration. ensure!( @@ -854,8 +869,6 @@ impl Pallet { } } - - // pub fn x( coldkey_a: &T::AccountId, coldkey_b: &T::AccountId ) -> Weight { // let mut weight = frame_support::weights::Weight::from_parts(0, 0); @@ -905,5 +918,4 @@ impl Pallet { // weight // } - } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index a3893dde3..a9a47db4d 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -1,8 +1,8 @@ use super::*; -use sp_core::Get; use frame_support::traits::fungible::Mutate; use frame_support::traits::tokens::Preservation; use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use sp_core::Get; impl Pallet { /// Swaps the hotkey of a coldkey account. @@ -30,7 +30,10 @@ impl Pallet { new_hotkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); let mut weight = T::DbWeight::get().reads(2); @@ -119,7 +122,10 @@ impl Pallet { new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey_performing_swap = ensure_signed(origin)?; - ensure!(!Self::coldkey_in_arbitration(&coldkey_performing_swap), Error::::ColdkeyIsInArbitration); + ensure!( + !Self::coldkey_in_arbitration(&coldkey_performing_swap), + Error::::ColdkeyIsInArbitration + ); let mut weight: Weight = T::DbWeight::get().reads(2); @@ -135,9 +141,12 @@ impl Pallet { ); // Actually do the swap. - weight = weight.saturating_add(Self::perform_swap_coldkey( old_coldkey, new_coldkey ).map_err(|_| Error::::SwapError)?); + weight = weight.saturating_add( + Self::perform_swap_coldkey(old_coldkey, new_coldkey) + .map_err(|_| Error::::SwapError)?, + ); - Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64() ); + Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); weight.saturating_accrue(T::DbWeight::get().writes(1)); Self::deposit_event(Event::ColdkeySwapped { @@ -148,65 +157,91 @@ impl Pallet { Ok(Some(weight).into()) } - /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey over a delayed - /// to faciliate arbitration. + /// Checks if a coldkey is currently in arbitration. /// /// # Arguments /// - /// * `origin` - The origin of the call, which must be signed by the old coldkey. + /// * `coldkey` - The account ID of the coldkey to check. + /// + /// # Returns + /// + /// * `bool` - True if the coldkey is in arbitration, false otherwise. + /// + /// # Notes + /// + /// This function compares the arbitration block number of the coldkey with the current block number. + pub fn coldkey_in_arbitration(coldkey: &T::AccountId) -> bool { + ColdkeyArbitrationBlock::::get(coldkey) > Self::get_current_block_as_u64() + } + + /// Schedules a coldkey swap to a new coldkey with arbitration. + /// + /// # Arguments + /// + /// * `old_coldkey` - The account ID of the old coldkey. /// * `new_coldkey` - The account ID of the new coldkey. /// + /// # Returns + /// + /// * `DispatchResult` - The result of the dispatch. + /// /// # Errors /// /// This function will return an error if: - /// - The caller is the same key as the new key or if the new key is already in the arbitration keys. - /// - There are already 2 keys in arbitration for this coldkey. - /// - // The coldkey is in arbitration state. - pub fn coldkey_in_arbitration( coldkey: &T::AccountId ) -> bool { ColdkeyArbitrationBlock::::get(coldkey) > Self::get_current_block_as_u64() } + /// - The old coldkey is the same as the new coldkey. + /// - The new coldkey is already in the list of destination coldkeys. + /// - There are already 2 destination coldkeys for the old coldkey. + /// + /// # Notes + /// + /// This function ensures that the new coldkey is not already in the list of destination coldkeys. + /// If the list of destination coldkeys is empty or has only one entry, the new coldkey is added to the list. + /// If the list of destination coldkeys has two entries, the function returns an error. + /// If the new coldkey is added to the list for the first time, the arbitration block is set for the old coldkey. + /// The list of coldkeys to arbitrate at the arbitration block is updated. pub fn do_schedule_arbitrated_coldkey_swap( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResult { - // Catch spurious swaps. - ensure!( - *old_coldkey != *new_coldkey, - Error::::SameColdkey - ); + ensure!(*old_coldkey != *new_coldkey, Error::::SameColdkey); // Get current destination coldkeys. - let mut destination_coldkeys: Vec = ColdkeySwapDestinations::::get( old_coldkey.clone() ); + let mut destination_coldkeys: Vec = + ColdkeySwapDestinations::::get(old_coldkey.clone()); // Check if the new coldkey is already in the swap wallets list ensure!( - !destination_coldkeys.contains( new_coldkey ), + !destination_coldkeys.contains(new_coldkey), Error::::DuplicateColdkey ); - + // If the destinations keys are empty or have size 1 then we will add the new coldkey to the list. - if destination_coldkeys.len() == 0 as usize || destination_coldkeys.len() == 1 as usize { + if destination_coldkeys.is_empty() || destination_coldkeys.len() == 1_usize { // Add this wallet to exist in the destination list. - destination_coldkeys.push( new_coldkey.clone() ); - ColdkeySwapDestinations::::insert( old_coldkey.clone(), destination_coldkeys.clone() ); + destination_coldkeys.push(new_coldkey.clone()); + ColdkeySwapDestinations::::insert(old_coldkey.clone(), destination_coldkeys.clone()); } else { return Err(Error::::ColdkeyIsInArbitration.into()); } // It is the first time we have seen this key. - if destination_coldkeys.len() == 1 as usize { - + if destination_coldkeys.len() == 1_usize { // Set the arbitration block for this coldkey. - let arbitration_block: u64 = Self::get_current_block_as_u64() + ArbitrationPeriod::::get(); - ColdkeyArbitrationBlock::::insert( old_coldkey.clone(), arbitration_block ); + let arbitration_block: u64 = + Self::get_current_block_as_u64().saturating_add(ArbitrationPeriod::::get()); + ColdkeyArbitrationBlock::::insert(old_coldkey.clone(), arbitration_block); // Update the list of coldkeys arbitrate on this block. - let mut key_to_arbitrate_on_this_block: Vec = ColdkeysToArbitrateAtBlock::::get( arbitration_block ); - if !key_to_arbitrate_on_this_block.contains( &old_coldkey.clone() ) { - key_to_arbitrate_on_this_block.push( old_coldkey.clone() ); + let mut key_to_arbitrate_on_this_block: Vec = + ColdkeysToArbitrateAtBlock::::get(arbitration_block); + if !key_to_arbitrate_on_this_block.contains(&old_coldkey.clone()) { + key_to_arbitrate_on_this_block.push(old_coldkey.clone()); } - ColdkeysToArbitrateAtBlock::::insert( arbitration_block, key_to_arbitrate_on_this_block ); - + ColdkeysToArbitrateAtBlock::::insert( + arbitration_block, + key_to_arbitrate_on_this_block, + ); } // Return true. @@ -230,31 +265,32 @@ impl Pallet { log::debug!("Arbitrating coldkeys for block: {:?}", current_block); // Get the coldkeys to swap here and then remove them. - let source_coldkeys: Vec = ColdkeysToArbitrateAtBlock::::get( current_block ); - ColdkeysToArbitrateAtBlock::::remove( current_block ); + let source_coldkeys: Vec = + ColdkeysToArbitrateAtBlock::::get(current_block); + ColdkeysToArbitrateAtBlock::::remove(current_block); weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // Iterate over all keys in swap and call perform_swap_coldkey for each for coldkey_i in source_coldkeys.iter() { - // Get the wallets to swap to for this coldkey. - let destinations_coldkeys: Vec = ColdkeySwapDestinations::::get( coldkey_i ); - ColdkeySwapDestinations::::remove( &coldkey_i ); + let destinations_coldkeys: Vec = + ColdkeySwapDestinations::::get(coldkey_i); + ColdkeySwapDestinations::::remove(&coldkey_i); weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // If the wallets to swap is > 1 we do nothing. if destinations_coldkeys.len() > 1 { - // Update the arbitration period but we still have the same wallet to swap to. - let next_arbitrage_period: u64 = current_block + ArbitrationPeriod::::get(); - ColdkeyArbitrationBlock::::insert( coldkey_i.clone(), next_arbitrage_period ); - - } else if destinations_coldkeys.len() == 1 { + let next_arbitrage_period: u64 = + current_block.saturating_add(ArbitrationPeriod::::get()); + ColdkeyArbitrationBlock::::insert(coldkey_i.clone(), next_arbitrage_period); + } else if let Some(new_coldkey) = destinations_coldkeys.first() { // ONLY 1 wallet: Get the wallet to swap to. - let new_coldkey = &destinations_coldkeys[0]; - // Perform the swap. - if let Err(_) = Self::perform_swap_coldkey( &coldkey_i, new_coldkey ).map(|w| weight = weight.saturating_add(w)) { + if Self::perform_swap_coldkey(coldkey_i, new_coldkey) + .map(|w| weight = weight.saturating_add(w)) + .is_err() + { return Err("Failed to perform coldkey swap"); } } @@ -263,10 +299,15 @@ impl Pallet { Ok(weight) } - - pub fn perform_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId ) -> Result { - - log::info!("Performing swap for coldkey: {:?} to {:?}", old_coldkey, new_coldkey); + pub fn perform_swap_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) -> Result { + log::info!( + "Performing swap for coldkey: {:?} to {:?}", + old_coldkey, + new_coldkey + ); // Init the weight. let mut weight = frame_support::weights::Weight::from_parts(0, 0); @@ -291,12 +332,12 @@ impl Pallet { } // Swap the coldkey. - let total_balance:u64 = Self::get_coldkey_balance(&old_coldkey); + let total_balance: u64 = Self::get_coldkey_balance(old_coldkey); if total_balance > 0 { // Attempt to transfer the entire total balance to coldkeyB. if let Err(e) = T::Currency::transfer( - &old_coldkey, - &new_coldkey, + old_coldkey, + new_coldkey, total_balance, Preservation::Expendable, ) { @@ -677,19 +718,18 @@ impl Pallet { new_coldkey: &T::AccountId, weight: &mut Weight, ) { - // Swap the owners. let old_owned_hotkeys = OwnedHotkeys::::get(old_coldkey); for owned_key in old_owned_hotkeys.iter() { Owner::::insert(owned_key, new_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); } - OwnedHotkeys::::remove( old_coldkey.clone() ); - OwnedHotkeys::::insert( new_coldkey.clone(), old_owned_hotkeys ); + OwnedHotkeys::::remove(old_coldkey.clone()); + OwnedHotkeys::::insert(new_coldkey.clone(), old_owned_hotkeys); // Swap all the keys the coldkey is staking too. - let staking_hotkeys = StakingHotkeys::::get( old_coldkey ); - StakingHotkeys::::remove( old_coldkey.clone() ); + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + StakingHotkeys::::remove(old_coldkey.clone()); for hotkey in staking_hotkeys.iter() { // Remove the previous stake and re-insert it. let stake = Stake::::get(hotkey, old_coldkey); @@ -698,7 +738,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); } // Add the new staking keys value. - StakingHotkeys::::insert( new_coldkey.clone(), staking_hotkeys.clone() ); + StakingHotkeys::::insert(new_coldkey.clone(), staking_hotkeys.clone()); } /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. @@ -739,7 +779,7 @@ impl Pallet { /// /// * `bool` - True if the coldkey has any associated hotkeys, false otherwise. pub fn coldkey_has_associated_hotkeys(coldkey: &T::AccountId) -> bool { - StakingHotkeys::::get(coldkey).len() > 0 + !StakingHotkeys::::get(coldkey).is_empty() } /// Swaps the subnet owner from the old coldkey to the new coldkey for all networks where the old coldkey is the owner. @@ -768,5 +808,4 @@ impl Pallet { } weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } - } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 9c87590ed..f45cde2fc 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3177,24 +3177,32 @@ fn test_arbitrated_coldkey_swap_success() { vec![current_coldkey] ); log::info!("Drain block set correctly: {:?}", drain_block); - log::info!("Drain block {:?}", pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block)); + log::info!( + "Drain block {:?}", + pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block) + ); // Make 7200 * 7 blocks pass run_to_block(drain_block); // Run unstaking SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); - log::info!("Arbitrated coldkeys for block: {:?}", SubtensorModule::get_current_block_as_u64()); + log::info!( + "Arbitrated coldkeys for block: {:?}", + SubtensorModule::get_current_block_as_u64() + ); // Check the hotkey stake. assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 500); // Get the owner of the hotkey now new key. - assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), new_coldkey); + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), + new_coldkey + ); // Check that the balance has been transferred to the new coldkey assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 500); // The new key as the 500 - }); } @@ -3248,16 +3256,11 @@ fn test_arbitrated_coldkey_swap_no_balance() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Try to unstake and transfer - let result = SubtensorModule::do_schedule_arbitrated_coldkey_swap( - ¤t_coldkey, - &new_coldkey, - ); + let result = + SubtensorModule::do_schedule_arbitrated_coldkey_swap(¤t_coldkey, &new_coldkey); // Print the result - log::info!( - "Result of arbitrated_coldkey_swap: {:?}", - result - ); + log::info!("Result of arbitrated_coldkey_swap: {:?}", result); // Print final balances log::info!( @@ -3348,7 +3351,6 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { // Check that the balance has been transferred to the new coldkey assert_eq!(Balances::total_balance(&new_coldkey), initial_balance); assert_eq!(Balances::total_balance(¤t_coldkey), 0); - }); } @@ -3383,14 +3385,16 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 800); // Owner has changed - assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), new_coldkey); + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), + new_coldkey + ); // Check that the full balance has been transferred to the new coldkey assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 200); // Check that the full balance has been transferred to the new coldkey assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); - }); } @@ -3398,7 +3402,6 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { #[test] fn test_arbitrated_coldkey_swap_multiple_arbitrations() { new_test_ext(1).execute_with(|| { - // Create coldkey with two choices. let coldkey: AccountId = U256::from(1); let new_coldkey1: AccountId = U256::from(2); @@ -3412,52 +3415,86 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { register_ok_neuron(1, hotkey, coldkey, 0); // Owner schedules a swap for themselves. - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( &coldkey, &new_coldkey1 )); + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + &coldkey, + &new_coldkey1 + )); // Attacker schedules the second swap for themselves. - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( &coldkey, &new_coldkey2 )); + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + &coldkey, + &new_coldkey2 + )); // Both keys are added in swap destinations. - assert_eq!( pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), vec![new_coldkey1, new_coldkey2] ); + assert_eq!( + pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), + vec![new_coldkey1, new_coldkey2] + ); // Check that we are arbitrating next block. - assert_eq!( pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(SubtensorModule::get_current_block_as_u64() + 1), vec![coldkey] ); + assert_eq!( + pallet_subtensor::ColdkeysToArbitrateAtBlock::::get( + SubtensorModule::get_current_block_as_u64() + 1 + ), + vec![coldkey] + ); // Key is in arbitration. - assert!( SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); // Arbitrate next block. - assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get( &coldkey ), SubtensorModule::get_current_block_as_u64() + 1 ); + assert_eq!( + pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), + SubtensorModule::get_current_block_as_u64() + 1 + ); // Arbitrate. step_block(1); // Both keys are removed and a new period begins. - assert_eq!( pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), vec![] ); + assert_eq!( + pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), + vec![] + ); // Arbitration has been pushed back but there are no keys to add to the list to arbitrate. - assert_eq!( pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(SubtensorModule::get_current_block_as_u64() + 1), vec![] ); + assert_eq!( + pallet_subtensor::ColdkeysToArbitrateAtBlock::::get( + SubtensorModule::get_current_block_as_u64() + 1 + ), + vec![] + ); // Key is in arbitration. - assert!( SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); // Arbitrate next block. - assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get( &coldkey ), SubtensorModule::get_current_block_as_u64() + 1 ); + assert_eq!( + pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), + SubtensorModule::get_current_block_as_u64() + 1 + ); // Arbitrate. - step_block(1); + step_block(1); // Key is not in arbitration - assert!( !SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + assert!(!SubtensorModule::coldkey_in_arbitration(&coldkey)); // Owner schedules a swap for themselves leys go back into arbitration. - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( &coldkey, &new_coldkey1 )); + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + &coldkey, + &new_coldkey1 + )); // Key goes back into arbitration. - assert!( SubtensorModule::coldkey_in_arbitration( &coldkey ) ); + assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); // Arbitrate next block. - assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get( &coldkey ), SubtensorModule::get_current_block_as_u64() + 1 ); + assert_eq!( + pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), + SubtensorModule::get_current_block_as_u64() + 1 + ); // Arbitrate. step_block(1); @@ -3466,6 +3503,5 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey), 0); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey1), 1000); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 0); - }); } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 09dec6d8c..48541abb4 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1180,7 +1180,10 @@ fn test_swap_stake_for_coldkey() { let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; let mut weight = Weight::zero(); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount1 + stake_amount2 + 123456); + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + 123456, + ); add_network(1, 13, 0); register_ok_neuron(1, hotkey1, old_coldkey, 0); register_ok_neuron(1, hotkey2, old_coldkey, 0); @@ -1195,22 +1198,40 @@ fn test_swap_stake_for_coldkey() { stake_amount2 )); - // Get owned keys - assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![hotkey1, hotkey2]); + // Get owned keys + assert_eq!( + SubtensorModule::get_owned_hotkeys(&old_coldkey), + vec![hotkey1, hotkey2] + ); assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![]); // Get all staked keys. - assert_eq!(SubtensorModule::get_all_staked_hotkeys(&old_coldkey), vec![hotkey1, hotkey2]); - assert_eq!(SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![]); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&old_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&new_coldkey), + vec![] + ); // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); - // Get owned keys - assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey1, hotkey2]); + // Get owned keys + assert_eq!( + SubtensorModule::get_owned_hotkeys(&new_coldkey), + vec![hotkey1, hotkey2] + ); assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); // Get all staked keys. - assert_eq!(SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![hotkey1, hotkey2]); - assert_eq!(SubtensorModule::get_all_staked_hotkeys(&old_coldkey), vec![]); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&new_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&old_coldkey), + vec![] + ); // Verify the swap assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); @@ -1224,7 +1245,6 @@ fn test_swap_stake_for_coldkey() { }); } - #[test] fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { new_test_ext(1).execute_with(|| { From dad3925990ecddacf29d433d3b53345bfbb91b21 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 15:13:30 +0400 Subject: [PATCH 092/185] fix: tests , feat: benchmarks --- pallets/subtensor/src/benchmarks.rs | 35 ++++++++ pallets/subtensor/tests/staking.rs | 135 +++++++++++++++++++++++++--- scripts/benchmark.sh | 48 +++++----- 3 files changed, 182 insertions(+), 36 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 403ba413e..9d754ed65 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -428,4 +428,39 @@ reveal_weights { let _ = Subtensor::::commit_weights(::RuntimeOrigin::from(RawOrigin::Signed(hotkey.clone())), netuid, commit_hash); }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) + + schedule_arbitrated_coldkey_swap { + let seed: u32 = 1; + let old_coldkey: T::AccountId = account("OldColdkey", 0, seed); + let new_coldkey: T::AccountId = account("NewColdkey", 0, seed + 1); + let hotkey: T::AccountId = account("Hotkey", 0, seed); + + let netuid = 1u16; + let tempo = 1u16; + + // Initialize the network + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_network_registration_allowed(netuid, true); + // Subtensor::::set_network_pow_registration_allowed(netuid, true); + + // Add balance to the old coldkey account + let amount_to_be_staked = 1000000u32.into(); + Subtensor::::add_balance_to_coldkey_account(&old_coldkey.clone(), amount_to_be_staked); + + // // Register the hotkey with the old coldkey + // let block_number: u64 = Subtensor::::get_current_block_as_u64(); + // let (nonce, work): (u64, Vec) = Subtensor::::create_work_for_block_number( + // netuid, + // block_number, + // 3, + // &hotkey, + // ); + // Burned register the hotkey with the old coldkey + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(old_coldkey.clone()).into(), + netuid, + hotkey.clone() + )); + + }: schedule_arbitrated_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey) } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index f45cde2fc..83c318dbb 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3171,7 +3171,13 @@ fn test_arbitrated_coldkey_swap_success() { ); // Check that drain block is set correctly - let drain_block: u64 = 7200 * 7 + 1; + let drain_block: u64 = 7200 * 4 + 1; + + log::info!( + "ColdkeysToArbitrateAtBlock before scheduling: {:?}", + pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block) + ); + assert_eq!( pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block), vec![current_coldkey] @@ -3182,7 +3188,7 @@ fn test_arbitrated_coldkey_swap_success() { pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block) ); - // Make 7200 * 7 blocks pass + // Make 7200 * 4 blocks pass run_to_block(drain_block); // Run unstaking @@ -3327,13 +3333,10 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { &new_coldkey )); - // Make 7200 * 7 blocks pass - let drain_block: u64 = 7200 * 7 + 1; + // Make 7200 * 4 blocks pass + let drain_block: u64 = 7200 * 4 + 1; run_to_block(drain_block); - // Run unstaking - SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); - // Print final balances log::info!( "Final current_coldkey balance: {:?}", @@ -3374,13 +3377,10 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { &new_coldkey )); - // Make 7200 * 7 blocks pass - let drain_block: u64 = 7200 * 7 + 1; + // Make 7200 * 4 blocks pass + let drain_block: u64 = 7200 * 4 + 1; run_to_block(drain_block); - // Run unstaking - SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); - // Check that all stake has been removed assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 800); @@ -3505,3 +3505,114 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 0); }); } + +// TODO: Verify that we never want more than 2 destinations for a coldkey +#[test] +fn test_arbitrated_coldkey_swap_existing_destination() { + new_test_ext(1).execute_with(|| { + let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); + let another_coldkey = U256::from(4); + + // Schedule a swap to new_coldkey + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + ¤t_coldkey, + &new_coldkey + )); + + // Attempt to schedule a swap to the same new_coldkey again + assert_noop!( + SubtensorModule::do_schedule_arbitrated_coldkey_swap(¤t_coldkey, &new_coldkey), + Error::::DuplicateColdkey + ); + + // Schedule a swap to another_coldkey + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + ¤t_coldkey, + &another_coldkey + )); + + // Attempt to schedule a third swap + let third_coldkey = U256::from(5); + assert_noop!( + SubtensorModule::do_schedule_arbitrated_coldkey_swap(¤t_coldkey, &third_coldkey), + Error::::ColdkeyIsInArbitration + ); + }); +} + +#[test] +fn test_arbitration_period_extension() { + new_test_ext(1).execute_with(|| { + let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); + let another_coldkey = U256::from(4); + + // Schedule a swap to new_coldkey + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + ¤t_coldkey, + &new_coldkey + )); + + // Schedule a swap to another_coldkey + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + ¤t_coldkey, + &another_coldkey + )); + + // Check that the arbitration period is extended + let arbitration_block = + SubtensorModule::get_current_block_as_u64() + ArbitrationPeriod::::get(); + assert_eq!( + pallet_subtensor::ColdkeyArbitrationBlock::::get(current_coldkey), + arbitration_block + ); + }); +} + +#[test] +fn test_concurrent_arbitrated_coldkey_swaps() { + new_test_ext(1).execute_with(|| { + // Manually create accounts + let coldkey1: AccountId = U256::from(1); + let hotkey1: AccountId = U256::from(2); + let new_coldkey1: AccountId = U256::from(3); + + let coldkey2: AccountId = U256::from(4); + let hotkey2: AccountId = U256::from(5); + let new_coldkey2: AccountId = U256::from(6); + + // Add networks + let netuid1: u16 = 1; + let netuid2: u16 = 2; + add_network(netuid1, 13, 0); + add_network(netuid2, 13, 0); + + // Register neurons in different networks + register_ok_neuron(netuid1, hotkey1, coldkey1, 0); + register_ok_neuron(netuid2, hotkey2, coldkey2, 0); + + // Add balance to coldkeys + SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 1000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey2, 1000); + + // Schedule swaps for both coldkeys + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + &coldkey1, + &new_coldkey1 + )); + assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + &coldkey2, + &new_coldkey2 + )); + + // Make 7200 * 4 blocks pass + let drain_block: u64 = 7200 * 4 + 1; + run_to_block(drain_block); + + // Run arbitration + SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); + + // Check that the balances have been transferred correctly + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey1), 1000); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 1000); + }); +} diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 52bdaf2c5..4cba2ae7a 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -7,31 +7,31 @@ OUTPUT_FILE='benchmarking.txt' # Getting arguments from user while [[ $# -gt 0 ]]; do - case $1 in - -p | --bin-path) - BIN_PATH="$2" - shift - shift - ;; - -* | --*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") - shift - ;; - esac + case $1 in + -p | --bin-path) + BIN_PATH="$2" + shift + shift + ;; + -* | --*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + shift + ;; + esac done # Ensure binary exists before node-subtensor executions if [ ! -f $BIN_PATH ]; then - if [[ "$DEFAULT_BIN_PATH" == "$BIN_PATH" ]]; then - cargo build --profile production --features runtime-benchmarks - else - echo "Binary '$BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." - exit 1 - fi + if [[ "$DEFAULT_BIN_PATH" == "$BIN_PATH" ]]; then + cargo build --profile production --features runtime-benchmarks + else + echo "Binary '$BIN_PATH' does not exist. You can use -p or --bin-path to specify a different location." + exit 1 + fi fi # Build Temporary Spec @@ -39,8 +39,8 @@ $BIN_PATH build-spec --disable-default-bootnode --raw --chain local >$TMP_SPEC # Run benchmark $BIN_PATH benchmark pallet \ - --chain=$TMP_SPEC \ - --pallet pallet-subtensor --extrinsic 'benchmark_dissolve_network' \ - --output $OUTPUT_FILE +--chain=$TMP_SPEC \ +--pallet pallet-subtensor --extrinsic 'schedule_arbitrated_coldkey_swap' \ +--output $OUTPUT_FILE rm $TMP_SPEC From 9a3f72578b11c1fcd9972d425b04cbbafb0dab4a Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 16:04:48 +0400 Subject: [PATCH 093/185] fix: benchmarks --- pallets/subtensor/src/benchmarks.rs | 14 ++------------ pallets/subtensor/src/lib.rs | 6 +++--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 9d754ed65..f70b8bdad 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -441,20 +441,10 @@ reveal_weights { // Initialize the network Subtensor::::init_new_network(netuid, tempo); Subtensor::::set_network_registration_allowed(netuid, true); - // Subtensor::::set_network_pow_registration_allowed(netuid, true); // Add balance to the old coldkey account - let amount_to_be_staked = 1000000u32.into(); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey.clone(), amount_to_be_staked); - - // // Register the hotkey with the old coldkey - // let block_number: u64 = Subtensor::::get_current_block_as_u64(); - // let (nonce, work): (u64, Vec) = Subtensor::::create_work_for_block_number( - // netuid, - // block_number, - // 3, - // &hotkey, - // ); + let amount_to_be_staked: u64 = 1000000u32.into(); + Subtensor::::add_balance_to_coldkey_account(&old_coldkey.clone(), amount_to_be_staked+1000000000); // Burned register the hotkey with the old coldkey assert_ok!(Subtensor::::burned_register( RawOrigin::Signed(old_coldkey.clone()).into(), diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 3756fd98a..2eb5d4df2 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2075,9 +2075,9 @@ pub mod pallet { /// /// Weight is calculated based on the number of database reads and writes. #[pallet::call_index(72)] - #[pallet::weight((Weight::from_parts(1_940_000_000, 0) - .saturating_add(T::DbWeight::get().reads(272)) - .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + #[pallet::weight((Weight::from_parts(21_000_000, 0) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Operational, Pays::No))] pub fn schedule_arbitrated_coldkey_swap( origin: OriginFor, new_coldkey: T::AccountId, From f32c54614ecd8cd38216d2b8f98c22af1c618356 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 19:13:55 +0400 Subject: [PATCH 094/185] chore: whitelist schedule_arbitrated_coldkey_swap and set_weights, set_root_weights --- runtime/src/lib.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 71b506247..01595a75f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -312,26 +312,9 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) | RuntimeCall::SubtensorModule( - pallet_subtensor::Call::add_stake { .. } - | pallet_subtensor::Call::become_delegate { .. } - | pallet_subtensor::Call::burned_register { .. } - | pallet_subtensor::Call::commit_weights { .. } - | pallet_subtensor::Call::decrease_take { .. } - | pallet_subtensor::Call::faucet { .. } - | pallet_subtensor::Call::increase_take { .. } - | pallet_subtensor::Call::register { .. } - | pallet_subtensor::Call::register_network { .. } - | pallet_subtensor::Call::remove_stake { .. } - | pallet_subtensor::Call::reveal_weights { .. } - | pallet_subtensor::Call::root_register { .. } - | pallet_subtensor::Call::serve_axon { .. } - | pallet_subtensor::Call::serve_prometheus { .. } - | pallet_subtensor::Call::set_root_weights { .. } + pallet_subtensor::Call::do_schedule_arbitrated_coldkey_swap { .. } | pallet_subtensor::Call::set_weights { .. } - | pallet_subtensor::Call::sudo { .. } - | pallet_subtensor::Call::sudo_unchecked_weight { .. } - | pallet_subtensor::Call::swap_hotkey { .. } - | pallet_subtensor::Call::vote { .. } + | pallet_subtensor::Call::set_root_weights { .. } ) ) } From 68df4e18fe0664b645aa022cf59a149b54fb9e98 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 22:06:56 +0400 Subject: [PATCH 095/185] feat: signed extension arbitration , swap cold key min amount --- pallets/subtensor/src/benchmarks.rs | 4 +- pallets/subtensor/src/lib.rs | 14 +- pallets/subtensor/src/swap.rs | 48 ++++-- pallets/subtensor/tests/staking.rs | 231 ++++++++++++++++++++++++---- runtime/src/lib.rs | 2 +- scripts/benchmark.sh | 2 +- 6 files changed, 254 insertions(+), 47 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index f70b8bdad..cb5bbff8e 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -429,7 +429,7 @@ reveal_weights { }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) - schedule_arbitrated_coldkey_swap { + schedule_coldkey_swap { let seed: u32 = 1; let old_coldkey: T::AccountId = account("OldColdkey", 0, seed); let new_coldkey: T::AccountId = account("NewColdkey", 0, seed + 1); @@ -452,5 +452,5 @@ reveal_weights { hotkey.clone() )); - }: schedule_arbitrated_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey) + }: schedule_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 2eb5d4df2..3059ee84d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -410,7 +410,7 @@ pub mod pallet { pub type ColdkeyArbitrationBlock = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. - pub type ColdkeysToArbitrateAtBlock = + pub type ColdkeysToSwapAtBlock = StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; /// -- ITEM (switches liquid alpha on) @@ -1332,7 +1332,7 @@ pub mod pallet { // - The number of the block we are initializing. fn on_initialize(_block_number: BlockNumberFor) -> Weight { // Unstake all and transfer pending coldkeys - let swap_weight = match Self::arbitrate_coldkeys_this_block() { + let swap_weight = match Self::swap_coldkeys_this_block() { Ok(weight) => weight, Err(_) => Weight::from_parts(0, 0), }; @@ -2078,13 +2078,13 @@ pub mod pallet { #[pallet::weight((Weight::from_parts(21_000_000, 0) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Operational, Pays::No))] - pub fn schedule_arbitrated_coldkey_swap( + pub fn schedule_coldkey_swap( origin: OriginFor, new_coldkey: T::AccountId, ) -> DispatchResult { // Attain the calling coldkey from the origin. let old_coldkey: T::AccountId = ensure_signed(origin)?; - Self::do_schedule_arbitrated_coldkey_swap(&old_coldkey, &new_coldkey) + Self::do_schedule_coldkey_swap(&old_coldkey, &new_coldkey) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ @@ -2340,6 +2340,12 @@ where _info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { + if Pallet::::coldkey_in_arbitration(who) { + return Err(TransactionValidityError::Invalid( + InvalidTransaction::Call.into(), + )); + } + match call.is_sub_type() { Some(Call::commit_weights { netuid, .. }) => { if Self::check_weights_min_stake(who) { diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index a9a47db4d..48a3ca1cc 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -174,6 +174,31 @@ impl Pallet { ColdkeyArbitrationBlock::::get(coldkey) > Self::get_current_block_as_u64() } + /// Returns the remaining arbitration period for a given coldkey. + /// + /// # Arguments + /// + /// * `coldkey` - The account ID of the coldkey to check. + /// + /// # Returns + /// + /// * `u64` - The remaining arbitration period in blocks. + /// + /// + /// # Notes + /// + /// This function calculates the remaining arbitration period by subtracting the current block number + /// from the arbitration block number of the coldkey. + pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { + let current_block: u64 = Self::get_current_block_as_u64(); + let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); + if arbitration_block > current_block { + arbitration_block.saturating_sub(current_block) + } else { + 0 + } + } + /// Schedules a coldkey swap to a new coldkey with arbitration. /// /// # Arguments @@ -199,10 +224,15 @@ impl Pallet { /// If the list of destination coldkeys has two entries, the function returns an error. /// If the new coldkey is added to the list for the first time, the arbitration block is set for the old coldkey. /// The list of coldkeys to arbitrate at the arbitration block is updated. - pub fn do_schedule_arbitrated_coldkey_swap( + // TOOD: + // Check minimum amount of TAO + // Add POW functionality / Move Destination Coldkeys to a list that can take X amount of coldkey dests + pub fn do_schedule_coldkey_swap( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResult { + + // TODO: Check minimum amount of TAO // Catch spurious swaps. ensure!(*old_coldkey != *new_coldkey, Error::::SameColdkey); @@ -234,14 +264,11 @@ impl Pallet { // Update the list of coldkeys arbitrate on this block. let mut key_to_arbitrate_on_this_block: Vec = - ColdkeysToArbitrateAtBlock::::get(arbitration_block); + ColdkeysToSwapAtBlock::::get(arbitration_block); if !key_to_arbitrate_on_this_block.contains(&old_coldkey.clone()) { key_to_arbitrate_on_this_block.push(old_coldkey.clone()); } - ColdkeysToArbitrateAtBlock::::insert( - arbitration_block, - key_to_arbitrate_on_this_block, - ); + ColdkeysToSwapAtBlock::::insert(arbitration_block, key_to_arbitrate_on_this_block); } // Return true. @@ -257,17 +284,16 @@ impl Pallet { /// # Returns /// /// * `Weight` - The total weight consumed by the operation. - pub fn arbitrate_coldkeys_this_block() -> Result { + pub fn swap_coldkeys_this_block() -> Result { let mut weight = frame_support::weights::Weight::from_parts(0, 0); // Get the block number let current_block: u64 = Self::get_current_block_as_u64(); - log::debug!("Arbitrating coldkeys for block: {:?}", current_block); + log::debug!("Swapping coldkeys for block: {:?}", current_block); // Get the coldkeys to swap here and then remove them. - let source_coldkeys: Vec = - ColdkeysToArbitrateAtBlock::::get(current_block); - ColdkeysToArbitrateAtBlock::::remove(current_block); + let source_coldkeys: Vec = ColdkeysToSwapAtBlock::::get(current_block); + ColdkeysToSwapAtBlock::::remove(current_block); weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // Iterate over all keys in swap and call perform_swap_coldkey for each diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 83c318dbb..80e49de88 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,5 +1,8 @@ #![allow(clippy::unwrap_used)] +use frame_support::pallet_prelude::{ + InvalidTransaction, TransactionValidity, TransactionValidityError, +}; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::Config; mod mock; @@ -8,6 +11,7 @@ use frame_support::sp_runtime::DispatchError; use mock::*; use pallet_subtensor::*; use sp_core::{H256, U256}; +use sp_runtime::traits::SignedExtension; /*********************************************************** staking::add_stake() tests @@ -3159,7 +3163,7 @@ fn test_arbitrated_coldkey_swap_success() { new_test_ext(1).execute_with(|| { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3174,25 +3178,25 @@ fn test_arbitrated_coldkey_swap_success() { let drain_block: u64 = 7200 * 4 + 1; log::info!( - "ColdkeysToArbitrateAtBlock before scheduling: {:?}", - pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block) + "ColdkeysToSwapAtBlock before scheduling: {:?}", + pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block) ); assert_eq!( - pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block), + pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block), vec![current_coldkey] ); log::info!("Drain block set correctly: {:?}", drain_block); log::info!( "Drain block {:?}", - pallet_subtensor::ColdkeysToArbitrateAtBlock::::get(drain_block) + pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block) ); // Make 7200 * 4 blocks pass run_to_block(drain_block); // Run unstaking - SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); + SubtensorModule::swap_coldkeys_this_block().unwrap(); log::info!( "Arbitrated coldkeys for block: {:?}", SubtensorModule::get_current_block_as_u64() @@ -3219,10 +3223,7 @@ fn test_arbitrated_coldkey_swap_same_coldkey() { let (current_coldkey, _hotkey, _) = setup_test_environment(); assert_noop!( - SubtensorModule::do_schedule_arbitrated_coldkey_swap( - ¤t_coldkey, - ¤t_coldkey - ), + SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, ¤t_coldkey), Error::::SameColdkey ); }); @@ -3262,8 +3263,7 @@ fn test_arbitrated_coldkey_swap_no_balance() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Try to unstake and transfer - let result = - SubtensorModule::do_schedule_arbitrated_coldkey_swap(¤t_coldkey, &new_coldkey); + let result = SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, &new_coldkey); // Print the result log::info!("Result of arbitrated_coldkey_swap: {:?}", result); @@ -3328,7 +3328,7 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { assert_eq!(Balances::total_balance(&new_coldkey), 0); // Perform unstake and transfer - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3372,7 +3372,7 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { 300 )); - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &new_coldkey )); @@ -3415,13 +3415,13 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { register_ok_neuron(1, hotkey, coldkey, 0); // Owner schedules a swap for themselves. - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey, &new_coldkey1 )); // Attacker schedules the second swap for themselves. - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey, &new_coldkey2 )); @@ -3434,7 +3434,7 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // Check that we are arbitrating next block. assert_eq!( - pallet_subtensor::ColdkeysToArbitrateAtBlock::::get( + pallet_subtensor::ColdkeysToSwapAtBlock::::get( SubtensorModule::get_current_block_as_u64() + 1 ), vec![coldkey] @@ -3460,7 +3460,7 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // Arbitration has been pushed back but there are no keys to add to the list to arbitrate. assert_eq!( - pallet_subtensor::ColdkeysToArbitrateAtBlock::::get( + pallet_subtensor::ColdkeysToSwapAtBlock::::get( SubtensorModule::get_current_block_as_u64() + 1 ), vec![] @@ -3482,7 +3482,7 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { assert!(!SubtensorModule::coldkey_in_arbitration(&coldkey)); // Owner schedules a swap for themselves leys go back into arbitration. - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey, &new_coldkey1 )); @@ -3514,19 +3514,19 @@ fn test_arbitrated_coldkey_swap_existing_destination() { let another_coldkey = U256::from(4); // Schedule a swap to new_coldkey - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &new_coldkey )); // Attempt to schedule a swap to the same new_coldkey again assert_noop!( - SubtensorModule::do_schedule_arbitrated_coldkey_swap(¤t_coldkey, &new_coldkey), + SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, &new_coldkey), Error::::DuplicateColdkey ); // Schedule a swap to another_coldkey - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &another_coldkey )); @@ -3534,7 +3534,7 @@ fn test_arbitrated_coldkey_swap_existing_destination() { // Attempt to schedule a third swap let third_coldkey = U256::from(5); assert_noop!( - SubtensorModule::do_schedule_arbitrated_coldkey_swap(¤t_coldkey, &third_coldkey), + SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, &third_coldkey), Error::::ColdkeyIsInArbitration ); }); @@ -3547,13 +3547,13 @@ fn test_arbitration_period_extension() { let another_coldkey = U256::from(4); // Schedule a swap to new_coldkey - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &new_coldkey )); // Schedule a swap to another_coldkey - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &another_coldkey )); @@ -3595,11 +3595,11 @@ fn test_concurrent_arbitrated_coldkey_swaps() { SubtensorModule::add_balance_to_coldkey_account(&coldkey2, 1000); // Schedule swaps for both coldkeys - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey1, &new_coldkey1 )); - assert_ok!(SubtensorModule::do_schedule_arbitrated_coldkey_swap( + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey2, &new_coldkey2 )); @@ -3609,10 +3609,185 @@ fn test_concurrent_arbitrated_coldkey_swaps() { run_to_block(drain_block); // Run arbitration - SubtensorModule::arbitrate_coldkeys_this_block().unwrap(); + SubtensorModule::swap_coldkeys_this_block().unwrap(); // Check that the balances have been transferred correctly assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey1), 1000); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 1000); }); } + +#[test] +fn test_get_remaining_arbitration_period() { + new_test_ext(1).execute_with(|| { + let coldkey_account_id = U256::from(12345); // arbitrary coldkey + let new_coldkey_account_id = U256::from(54321); // arbitrary new coldkey + + // Schedule a coldkey swap to set the arbitration block + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &coldkey_account_id, + &new_coldkey_account_id + )); + + // Get the current block number and arbitration period + let current_block: u64 = SubtensorModule::get_current_block_as_u64(); + let arbitration_period: u64 = ArbitrationPeriod::::get(); + log::info!("arbitration_period: {:?}", arbitration_period); + let arbitration_block: u64 = current_block + arbitration_period; + log::info!("arbitration_block: {:?}", arbitration_block); + + // Check if the remaining arbitration period is correct + let remaining_period = + SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); + assert_eq!(remaining_period, arbitration_period); + + // Move the current block forward and check again + step_block(50); + let remaining_period = + SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); + assert_eq!(remaining_period, arbitration_period - 50); + + // Move the current block beyond the arbitration block and check again + step_block((arbitration_period as u16) - 50 + 1); + let remaining_period = + SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); + assert_eq!(remaining_period, 0); + }); +} + +#[test] +fn test_coldkey_in_arbitration() { + new_test_ext(1).execute_with(|| { + let coldkey_account_id = U256::from(1); + let hotkey_account_id = U256::from(2); + let new_coldkey_account_id = U256::from(3); + + // Schedule a coldkey swap to put the coldkey in arbitration + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &coldkey_account_id, + &new_coldkey_account_id + )); + + let call = RuntimeCall::SubtensorModule(crate::Call::add_stake { + hotkey: hotkey_account_id, + amount_staked: 1000, + }); + + assert_eq!( + validate_transaction(&coldkey_account_id, &call), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); + }); +} + +#[test] +fn test_add_stake_coldkey_in_arbitration() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let new_coldkey_account_id = U256::from(71337); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + + // Schedule a coldkey swap to put the coldkey in arbitration + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &coldkey_account_id, + &new_coldkey_account_id + )); + + let call = RuntimeCall::SubtensorModule(crate::Call::add_stake { + hotkey: hotkey_account_id, + amount_staked: 1000, + }); + + assert_eq!( + validate_transaction(&coldkey_account_id, &call), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); + }) +} + +#[test] +fn test_remove_stake_coldkey_in_arbitration() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let new_coldkey_account_id = U256::from(71337); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); + + // Schedule a coldkey swap to put the coldkey in arbitration + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &coldkey_account_id, + &new_coldkey_account_id + )); + + let call = RuntimeCall::SubtensorModule(crate::Call::remove_stake { + hotkey: hotkey_account_id, + amount_unstaked: 500, + }); + + assert_eq!( + validate_transaction(&coldkey_account_id, &call), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); + }); +} + +#[test] +fn test_add_stake_coldkey_not_in_arbitration() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 1000 + )); + }); +} + +#[test] +fn test_remove_stake_coldkey_not_in_arbitration() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); + + assert_ok!(SubtensorModule::remove_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 500 + )); + }); +} + +fn validate_transaction(who: &AccountId, call: &RuntimeCall) -> TransactionValidity { + SubtensorSignedExtension::::new().validate(who, call, &DispatchInfo::default(), 0) +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 01595a75f..7143986eb 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -312,7 +312,7 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) | RuntimeCall::SubtensorModule( - pallet_subtensor::Call::do_schedule_arbitrated_coldkey_swap { .. } + pallet_subtensor::Call::schedule_coldkey_swap { .. } | pallet_subtensor::Call::set_weights { .. } | pallet_subtensor::Call::set_root_weights { .. } ) diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh index 4cba2ae7a..8f54fa54a 100755 --- a/scripts/benchmark.sh +++ b/scripts/benchmark.sh @@ -40,7 +40,7 @@ $BIN_PATH build-spec --disable-default-bootnode --raw --chain local >$TMP_SPEC # Run benchmark $BIN_PATH benchmark pallet \ --chain=$TMP_SPEC \ ---pallet pallet-subtensor --extrinsic 'schedule_arbitrated_coldkey_swap' \ +--pallet pallet-subtensor --extrinsic 'schedule_coldkey_swap' \ --output $OUTPUT_FILE rm $TMP_SPEC From 507e8cf0fd7a2c8d87cbf1043ce8cfa78aa602b5 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 22:13:12 +0400 Subject: [PATCH 096/185] chore: lints --- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/swap.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 3059ee84d..b05a67047 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2342,7 +2342,7 @@ where ) -> TransactionValidity { if Pallet::::coldkey_in_arbitration(who) { return Err(TransactionValidityError::Invalid( - InvalidTransaction::Call.into(), + InvalidTransaction::Call, )); } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 48a3ca1cc..aa99a649b 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -226,13 +226,12 @@ impl Pallet { /// The list of coldkeys to arbitrate at the arbitration block is updated. // TOOD: // Check minimum amount of TAO - // Add POW functionality / Move Destination Coldkeys to a list that can take X amount of coldkey dests + // Add POW functionality / Move Destination Coldkeys to a list that can take X amount of coldkey dests pub fn do_schedule_coldkey_swap( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResult { - - // TODO: Check minimum amount of TAO + // TODO: Check minimum amount of TAO // Catch spurious swaps. ensure!(*old_coldkey != *new_coldkey, Error::::SameColdkey); From 0bcdd2616a1ffcf20283e8aa963c722899433ab4 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 22:56:30 +0400 Subject: [PATCH 097/185] feat: signed extensions , reduce arbitration time --- pallets/subtensor/src/lib.rs | 34 ++++++++++++--- pallets/subtensor/tests/staking.rs | 70 ++++++++++-------------------- 2 files changed, 50 insertions(+), 54 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b05a67047..95ca4d54a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -18,6 +18,7 @@ use frame_support::{ use codec::{Decode, Encode}; use frame_support::sp_runtime::transaction_validity::InvalidTransaction; use frame_support::sp_runtime::transaction_validity::ValidTransaction; +use pallet_balances::Call as BalancesCall; use scale_info::TypeInfo; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, @@ -392,7 +393,7 @@ pub mod pallet { #[pallet::type_value] /// Default stake interval. pub fn DefaultArbitrationPeriod() -> u64 { - 7200 * 4 + 7200 * 3 } #[pallet::storage] // ---- StorageItem Global Used Work. pub type ArbitrationPeriod = @@ -2317,10 +2318,12 @@ impl sp_std::fmt::Debug for SubtensorSignedE } } -impl SignedExtension for SubtensorSignedExtension +impl SignedExtension + for SubtensorSignedExtension where T::RuntimeCall: Dispatchable, ::RuntimeCall: IsSubType>, + ::RuntimeCall: IsSubType>, { const IDENTIFIER: &'static str = "SubtensorSignedExtension"; @@ -2340,12 +2343,19 @@ where _info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { - if Pallet::::coldkey_in_arbitration(who) { - return Err(TransactionValidityError::Invalid( - InvalidTransaction::Call, - )); + // Check if the call is one of the balance transfer types we want to reject + if let Some(balances_call) = call.is_sub_type() { + match balances_call { + BalancesCall::transfer_allow_death { .. } + | BalancesCall::transfer_keep_alive { .. } + | BalancesCall::transfer_all { .. } => { + if Pallet::::coldkey_in_arbitration(who) { + return Err(TransactionValidityError::Invalid(InvalidTransaction::Call)); + } + } + _ => {} // Other Balances calls are allowed + } } - match call.is_sub_type() { Some(Call::commit_weights { netuid, .. }) => { if Self::check_weights_min_stake(who) { @@ -2422,6 +2432,16 @@ where priority: Self::get_priority_vanilla(), ..Default::default() }), + Some(Call::dissolve_network { .. }) => { + if Pallet::::coldkey_in_arbitration(who) { + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + } else { + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } + } _ => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 80e49de88..0a9e88117 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -9,6 +9,7 @@ mod mock; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; use frame_support::sp_runtime::DispatchError; use mock::*; +use pallet_balances::Call as BalancesCall; use pallet_subtensor::*; use sp_core::{H256, U256}; use sp_runtime::traits::SignedExtension; @@ -3175,7 +3176,7 @@ fn test_arbitrated_coldkey_swap_success() { ); // Check that drain block is set correctly - let drain_block: u64 = 7200 * 4 + 1; + let drain_block: u64 = 7200 * 3 + 1; log::info!( "ColdkeysToSwapAtBlock before scheduling: {:?}", @@ -3656,21 +3657,25 @@ fn test_get_remaining_arbitration_period() { } #[test] -fn test_coldkey_in_arbitration() { +fn test_transfer_coldkey_in_arbitration() { new_test_ext(1).execute_with(|| { let coldkey_account_id = U256::from(1); - let hotkey_account_id = U256::from(2); + let recipient_account_id = U256::from(2); let new_coldkey_account_id = U256::from(3); + // Add balance to coldkey + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey_account_id, &new_coldkey_account_id )); - let call = RuntimeCall::SubtensorModule(crate::Call::add_stake { - hotkey: hotkey_account_id, - amount_staked: 1000, + // Try to transfer balance + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: recipient_account_id.into(), + value: 1000, }); assert_eq!( @@ -3705,10 +3710,8 @@ fn test_add_stake_coldkey_in_arbitration() { amount_staked: 1000, }); - assert_eq!( - validate_transaction(&coldkey_account_id, &call), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); + // This should now be Ok + assert!(validate_transaction(&coldkey_account_id, &call).is_ok()); }) } @@ -3738,53 +3741,26 @@ fn test_remove_stake_coldkey_in_arbitration() { amount_unstaked: 500, }); - assert_eq!( - validate_transaction(&coldkey_account_id, &call), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); + // This should now be Ok + assert!(validate_transaction(&coldkey_account_id, &call).is_ok()); }); } #[test] -fn test_add_stake_coldkey_not_in_arbitration() { +fn test_transfer_coldkey_not_in_arbitration() { new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(561337); let coldkey_account_id = U256::from(61337); - let netuid: u16 = 1; - let start_nonce: u64 = 0; - let tempo: u16 = 13; + let recipient_account_id = U256::from(71337); - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - 1000 - )); - }); -} - -#[test] -fn test_remove_stake_coldkey_not_in_arbitration() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(561337); - let coldkey_account_id = U256::from(61337); - let netuid: u16 = 1; - let start_nonce: u64 = 0; - let tempo: u16 = 13; - - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); - SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: recipient_account_id.into(), + value: 1000, + }); - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - 500 - )); + // This should be Ok + assert!(validate_transaction(&coldkey_account_id, &call).is_ok()); }); } From b41a2d55a56bc9b02995b1ee3fc48cd3490f7a6b Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 7 Jul 2024 23:12:10 +0400 Subject: [PATCH 098/185] lints, bump spec version --- pallets/subtensor/src/migration.rs | 9 ++++----- runtime/src/lib.rs | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 400030450..35b3c4260 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -565,23 +565,23 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { if stake > 0 { let mut hotkeys = StakingHotkeys::::get(&coldkey); storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage - + // Add the hotkey if it's not already in the vector if !hotkeys.contains(&hotkey) { hotkeys.push(hotkey); keys_touched = keys_touched.saturating_add(1); - + // Update longest hotkey vector info if longest_hotkey_vector < hotkeys.len() { longest_hotkey_vector = hotkeys.len(); longest_coldkey = Some(coldkey.clone()); } - + // Update the StakingHotkeys storage StakingHotkeys::::insert(&coldkey, hotkeys); storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage } - + // Accrue weight for reads and writes weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); } @@ -603,4 +603,3 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { Weight::zero() } } - diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 71b506247..45096d33b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 159, + spec_version: 160, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -294,7 +294,7 @@ parameter_types! { pub const RootEnterDuration: BlockNumber = 5 * 60 * 24; // 24 hours - pub const RootExtendDuration: BlockNumber = 5 * 60 * 3; // 3 hours + pub const RootExtendDuration: BlockNumber = 5 * 60 * 12; // 12 hours pub const DisallowPermissionlessEntering: Option = None; pub const DisallowPermissionlessExtending: Option = None; From cc9c0946c6d3a9ae57f2ad5bd442ef98e5916a78 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 02:19:29 +0400 Subject: [PATCH 099/185] feat: pow coldkey swaps ,todo fix tests , dont clear map during abritration --- pallets/subtensor/src/errors.rs | 2 + pallets/subtensor/src/lib.rs | 5 +- pallets/subtensor/src/swap.rs | 66 ++++++--- pallets/subtensor/tests/staking.rs | 219 +++++++++++++++++++++++------ 4 files changed, 228 insertions(+), 64 deletions(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index cc1aa2055..3bfae3cef 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -152,5 +152,7 @@ mod errors { DuplicateColdkey, /// Error thrown on a coldkey swap. SwapError, + /// Insufficient Balance to Schedule coldkey swap + InsufficientBalanceToPerformColdkeySwap, } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 95ca4d54a..96f3798da 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2082,10 +2082,13 @@ pub mod pallet { pub fn schedule_coldkey_swap( origin: OriginFor, new_coldkey: T::AccountId, + work: Vec, + block_number: u64, + nonce: u64, ) -> DispatchResult { // Attain the calling coldkey from the origin. let old_coldkey: T::AccountId = ensure_signed(origin)?; - Self::do_schedule_coldkey_swap(&old_coldkey, &new_coldkey) + Self::do_schedule_coldkey_swap(&old_coldkey, &new_coldkey, work, block_number, nonce) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index aa99a649b..0187aa436 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -2,7 +2,7 @@ use super::*; use frame_support::traits::fungible::Mutate; use frame_support::traits::tokens::Preservation; use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; -use sp_core::Get; +use sp_core::{Get, U256}; impl Pallet { /// Swaps the hotkey of a coldkey account. @@ -203,8 +203,11 @@ impl Pallet { /// /// # Arguments /// - /// * `old_coldkey` - The account ID of the old coldkey. + /// * `origin` - The origin of the call, which must be signed by the old coldkey. /// * `new_coldkey` - The account ID of the new coldkey. + /// * `work` - The proof of work submitted by the caller. + /// * `block_number` - The block number at which the work was performed. + /// * `nonce` - The nonce used for the proof of work. /// /// # Returns /// @@ -216,64 +219,85 @@ impl Pallet { /// - The old coldkey is the same as the new coldkey. /// - The new coldkey is already in the list of destination coldkeys. /// - There are already 2 destination coldkeys for the old coldkey. + /// - The old coldkey doesn't have the minimum required TAO balance. + /// - The proof of work is invalid or doesn't meet the required difficulty. /// /// # Notes /// /// This function ensures that the new coldkey is not already in the list of destination coldkeys. - /// If the list of destination coldkeys is empty or has only one entry, the new coldkey is added to the list. - /// If the list of destination coldkeys has two entries, the function returns an error. - /// If the new coldkey is added to the list for the first time, the arbitration block is set for the old coldkey. - /// The list of coldkeys to arbitrate at the arbitration block is updated. - // TOOD: - // Check minimum amount of TAO - // Add POW functionality / Move Destination Coldkeys to a list that can take X amount of coldkey dests + /// It also checks for a minimum TAO balance and verifies the proof of work. + /// The difficulty of the proof of work increases exponentially with each subsequent call. pub fn do_schedule_coldkey_swap( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, + work: Vec, + block_number: u64, + nonce: u64, ) -> DispatchResult { - // TODO: Check minimum amount of TAO - // Catch spurious swaps. - ensure!(*old_coldkey != *new_coldkey, Error::::SameColdkey); + ensure!(old_coldkey != new_coldkey, Error::::SameColdkey); + + // Check minimum amount of TAO (1 TAO) + let minimum_balance: u64 = 1_000_000_000; // 1 TAO in RAO + ensure!( + Self::get_coldkey_balance(&old_coldkey) >= minimum_balance, + Error::::InsufficientBalanceToPerformColdkeySwap + ); - // Get current destination coldkeys. + // Get current destination coldkeys let mut destination_coldkeys: Vec = ColdkeySwapDestinations::::get(old_coldkey.clone()); + // Calculate difficulty based on the number of existing destination coldkeys + let difficulty = Self::calculate_pow_difficulty(destination_coldkeys.len() as u32); + let work_hash = Self::vec_to_hash(work.clone()); + ensure!( + Self::hash_meets_difficulty(&work_hash, difficulty), + Error::::InvalidDifficulty + ); + + // Verify work is the product of the nonce, the block number, and coldkey + let seal = Self::create_seal_hash(block_number, nonce, &old_coldkey); + ensure!(seal == work_hash, Error::::InvalidSeal); + // Check if the new coldkey is already in the swap wallets list ensure!( - !destination_coldkeys.contains(new_coldkey), + !destination_coldkeys.contains(&new_coldkey), Error::::DuplicateColdkey ); - // If the destinations keys are empty or have size 1 then we will add the new coldkey to the list. + // If the destinations keys are empty or have size 1 then we will add the new coldkey to the list if destination_coldkeys.is_empty() || destination_coldkeys.len() == 1_usize { - // Add this wallet to exist in the destination list. destination_coldkeys.push(new_coldkey.clone()); ColdkeySwapDestinations::::insert(old_coldkey.clone(), destination_coldkeys.clone()); } else { return Err(Error::::ColdkeyIsInArbitration.into()); } - // It is the first time we have seen this key. + // It is the first time we have seen this key if destination_coldkeys.len() == 1_usize { - // Set the arbitration block for this coldkey. + // Set the arbitration block for this coldkey let arbitration_block: u64 = Self::get_current_block_as_u64().saturating_add(ArbitrationPeriod::::get()); ColdkeyArbitrationBlock::::insert(old_coldkey.clone(), arbitration_block); - // Update the list of coldkeys arbitrate on this block. + // Update the list of coldkeys to arbitrate on this block let mut key_to_arbitrate_on_this_block: Vec = ColdkeysToSwapAtBlock::::get(arbitration_block); - if !key_to_arbitrate_on_this_block.contains(&old_coldkey.clone()) { + if !key_to_arbitrate_on_this_block.contains(&old_coldkey) { key_to_arbitrate_on_this_block.push(old_coldkey.clone()); } ColdkeysToSwapAtBlock::::insert(arbitration_block, key_to_arbitrate_on_this_block); } - // Return true. Ok(()) } + /// Calculate the proof of work difficulty based on the number of swap attempts + fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { + let base_difficulty: U256 = U256::from(1_000_000); // Base difficulty + base_difficulty * U256::from(2).pow(U256::from(swap_attempts)) + } + /// Arbitrates coldkeys that are scheduled to be swapped on this block. /// /// This function retrieves the list of coldkeys scheduled to be swapped on the current block, diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 0a9e88117..8fa0c2e17 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3164,9 +3164,15 @@ fn test_arbitrated_coldkey_swap_success() { new_test_ext(1).execute_with(|| { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &new_coldkey + ¤t_coldkey.clone(), + &new_coldkey, + work, + current_block, + nonce )); // Check that ColdkeySwapDestinations is populated correctly @@ -3223,8 +3229,17 @@ fn test_arbitrated_coldkey_swap_same_coldkey() { new_test_ext(1).execute_with(|| { let (current_coldkey, _hotkey, _) = setup_test_environment(); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + assert_noop!( - SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, ¤t_coldkey), + SubtensorModule::do_schedule_coldkey_swap( + ¤t_coldkey.clone(), + ¤t_coldkey, + work, + current_block, + nonce + ), Error::::SameColdkey ); }); @@ -3263,12 +3278,28 @@ fn test_arbitrated_coldkey_swap_no_balance() { assert_eq!(Balances::total_balance(&hotkey), 0); assert_eq!(Balances::total_balance(&new_coldkey), 0); - // Try to unstake and transfer - let result = SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, &new_coldkey); + // Generate valid PoW + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + + // Try to schedule coldkey swap + let result = SubtensorModule::do_schedule_coldkey_swap( + ¤t_coldkey.clone(), + &new_coldkey, + work, + current_block, + nonce, + ); // Print the result log::info!("Result of arbitrated_coldkey_swap: {:?}", result); + // Verify that the operation failed due to insufficient balance + assert_noop!( + result, + Error::::InsufficientBalanceToPerformColdkeySwap + ); + // Print final balances log::info!( "Final current_coldkey balance: {:?}", @@ -3328,14 +3359,20 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { assert_eq!(Balances::total_balance(&hotkey), 0); assert_eq!(Balances::total_balance(&new_coldkey), 0); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + // Perform unstake and transfer assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &new_coldkey + ¤t_coldkey.clone(), + &new_coldkey, + work, + current_block, + nonce )); // Make 7200 * 4 blocks pass - let drain_block: u64 = 7200 * 4 + 1; + let drain_block: u64 = 7200 * 3 + 1; run_to_block(drain_block); // Print final balances @@ -3373,13 +3410,19 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { 300 )); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &new_coldkey + ¤t_coldkey.clone(), + &new_coldkey, + work, + current_block, + nonce )); // Make 7200 * 4 blocks pass - let drain_block: u64 = 7200 * 4 + 1; + let drain_block: u64 = 7200 * 3 + 1; run_to_block(drain_block); // Check that all stake has been removed @@ -3415,16 +3458,26 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { ArbitrationPeriod::::put(1); // One block arbitration period. register_ok_neuron(1, hotkey, coldkey, 0); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work1, nonce1) = generate_valid_pow(&coldkey, current_block, 1_000_000); + let (work2, nonce2) = generate_valid_pow(&coldkey, current_block, 2_000_000); + // Owner schedules a swap for themselves. assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey, - &new_coldkey1 + &coldkey.clone(), + &new_coldkey1, + work1, + current_block, + nonce1 )); // Attacker schedules the second swap for themselves. assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey, - &new_coldkey2 + &coldkey.clone(), + &new_coldkey2, + work2, + current_block, + nonce2 )); // Both keys are added in swap destinations. @@ -3482,10 +3535,15 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // Key is not in arbitration assert!(!SubtensorModule::coldkey_in_arbitration(&coldkey)); - // Owner schedules a swap for themselves leys go back into arbitration. + // Owner schedules a swap for themselves lets go back into arbitration. + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work3, nonce3) = generate_valid_pow(&coldkey, current_block, 4_000_000); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey, - &new_coldkey1 + &coldkey.clone(), + &new_coldkey1, + work3, + current_block, + nonce3 )); // Key goes back into arbitration. @@ -3514,28 +3572,51 @@ fn test_arbitrated_coldkey_swap_existing_destination() { let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); let another_coldkey = U256::from(4); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, 2_000_000); + let (work3, nonce3) = generate_valid_pow(¤t_coldkey, current_block, 4_000_000); + // Schedule a swap to new_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, - &new_coldkey + &new_coldkey, + work1, + current_block, + nonce1 )); // Attempt to schedule a swap to the same new_coldkey again assert_noop!( - SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, &new_coldkey), + SubtensorModule::do_schedule_coldkey_swap( + ¤t_coldkey.clone(), + &new_coldkey, + work2.clone(), + current_block, + nonce2 + ), Error::::DuplicateColdkey ); // Schedule a swap to another_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &another_coldkey + ¤t_coldkey.clone(), + &another_coldkey, + work2, + current_block, + nonce2 )); // Attempt to schedule a third swap let third_coldkey = U256::from(5); assert_noop!( - SubtensorModule::do_schedule_coldkey_swap(¤t_coldkey, &third_coldkey), + SubtensorModule::do_schedule_coldkey_swap( + ¤t_coldkey.clone(), + &third_coldkey, + work3, + current_block, + nonce3 + ), Error::::ColdkeyIsInArbitration ); }); @@ -3547,16 +3628,26 @@ fn test_arbitration_period_extension() { let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); let another_coldkey = U256::from(4); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, 2_000_000); + // Schedule a swap to new_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &new_coldkey + ¤t_coldkey.clone(), + &new_coldkey, + work1, + current_block, + nonce1 )); // Schedule a swap to another_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &another_coldkey + ¤t_coldkey.clone(), + &another_coldkey, + work2, + current_block, + nonce2 )); // Check that the arbitration period is extended @@ -3595,18 +3686,27 @@ fn test_concurrent_arbitrated_coldkey_swaps() { SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 1000); SubtensorModule::add_balance_to_coldkey_account(&coldkey2, 1000); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work1, nonce1) = generate_valid_pow(&coldkey1, current_block, 1_000_000); + let (work2, nonce2) = generate_valid_pow(&coldkey2, current_block, 1_000_000); + // Schedule swaps for both coldkeys assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey1, - &new_coldkey1 + &coldkey1.clone(), + &new_coldkey1, + work1, + current_block, + nonce1 )); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey2, - &new_coldkey2 + &coldkey2.clone(), + &new_coldkey2, + work2, + current_block, + nonce2 )); - // Make 7200 * 4 blocks pass - let drain_block: u64 = 7200 * 4 + 1; + let drain_block: u64 = 7200 * 3 + 1; run_to_block(drain_block); // Run arbitration @@ -3624,10 +3724,16 @@ fn test_get_remaining_arbitration_period() { let coldkey_account_id = U256::from(12345); // arbitrary coldkey let new_coldkey_account_id = U256::from(54321); // arbitrary new coldkey + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + // Schedule a coldkey swap to set the arbitration block assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id, - &new_coldkey_account_id + &coldkey_account_id.clone(), + &new_coldkey_account_id, + work, + current_block, + nonce )); // Get the current block number and arbitration period @@ -3666,10 +3772,16 @@ fn test_transfer_coldkey_in_arbitration() { // Add balance to coldkey SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id, - &new_coldkey_account_id + &coldkey_account_id.clone(), + &new_coldkey_account_id, + work, + current_block, + nonce )); // Try to transfer balance @@ -3699,12 +3811,17 @@ fn test_add_stake_coldkey_in_arbitration() { register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id, - &new_coldkey_account_id + &coldkey_account_id.clone(), + &new_coldkey_account_id, + work, + current_block, + nonce )); - let call = RuntimeCall::SubtensorModule(crate::Call::add_stake { hotkey: hotkey_account_id, amount_staked: 1000, @@ -3730,10 +3847,16 @@ fn test_remove_stake_coldkey_in_arbitration() { SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id, - &new_coldkey_account_id + &coldkey_account_id.clone(), + &new_coldkey_account_id, + work, + current_block, + nonce )); let call = RuntimeCall::SubtensorModule(crate::Call::remove_stake { @@ -3767,3 +3890,15 @@ fn test_transfer_coldkey_not_in_arbitration() { fn validate_transaction(who: &AccountId, call: &RuntimeCall) -> TransactionValidity { SubtensorSignedExtension::::new().validate(who, call, &DispatchInfo::default(), 0) } + +// Helper function to generate valid PoW +fn generate_valid_pow(coldkey: &AccountId, block_number: u64, difficulty: u32) -> (Vec, u64) { + let mut nonce: u64 = 0; + loop { + let work = SubtensorModule::create_seal_hash(block_number, nonce, coldkey); + if SubtensorModule::hash_meets_difficulty(&work, difficulty.into()) { + return (work.as_bytes().to_vec(), nonce); + } + nonce += 1; + } +} From 60f371347dddc808adc2a4232574e59a24d2a024 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 8 Jul 2024 11:03:13 +0800 Subject: [PATCH 100/185] fix unit tests --- pallets/subtensor/src/lib.rs | 3 + pallets/subtensor/src/swap.rs | 10 ++-- pallets/subtensor/tests/staking.rs | 91 ++++++++++++++++++++++++------ 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 96f3798da..489e0ffe3 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -84,6 +84,9 @@ pub mod pallet { /// order of migrations. (i.e. always increasing) const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); + /// Minimum balance required to perform a coldkey swap + pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 1_000_000_000; // 1 TAO in RAO + #[pallet::pallet] #[pallet::without_storage_info] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 0187aa436..f5b10d824 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -1,4 +1,5 @@ use super::*; +use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; use frame_support::traits::fungible::Mutate; use frame_support::traits::tokens::Preservation; use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; @@ -237,9 +238,8 @@ impl Pallet { ensure!(old_coldkey != new_coldkey, Error::::SameColdkey); // Check minimum amount of TAO (1 TAO) - let minimum_balance: u64 = 1_000_000_000; // 1 TAO in RAO ensure!( - Self::get_coldkey_balance(&old_coldkey) >= minimum_balance, + Self::get_coldkey_balance(old_coldkey) >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, Error::::InsufficientBalanceToPerformColdkeySwap ); @@ -256,12 +256,12 @@ impl Pallet { ); // Verify work is the product of the nonce, the block number, and coldkey - let seal = Self::create_seal_hash(block_number, nonce, &old_coldkey); + let seal = Self::create_seal_hash(block_number, nonce, old_coldkey); ensure!(seal == work_hash, Error::::InvalidSeal); // Check if the new coldkey is already in the swap wallets list ensure!( - !destination_coldkeys.contains(&new_coldkey), + !destination_coldkeys.contains(new_coldkey), Error::::DuplicateColdkey ); @@ -283,7 +283,7 @@ impl Pallet { // Update the list of coldkeys to arbitrate on this block let mut key_to_arbitrate_on_this_block: Vec = ColdkeysToSwapAtBlock::::get(arbitration_block); - if !key_to_arbitrate_on_this_block.contains(&old_coldkey) { + if !key_to_arbitrate_on_this_block.contains(old_coldkey) { key_to_arbitrate_on_this_block.push(old_coldkey.clone()); } ColdkeysToSwapAtBlock::::insert(arbitration_block, key_to_arbitrate_on_this_block); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 8fa0c2e17..cb143d438 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3166,7 +3166,10 @@ fn test_arbitrated_coldkey_swap_success() { let current_block = SubtensorModule::get_current_block_as_u64(); let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); - + SubtensorModule::add_balance_to_coldkey_account( + ¤t_coldkey, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, @@ -3219,7 +3222,10 @@ fn test_arbitrated_coldkey_swap_success() { ); // Check that the balance has been transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 500); // The new key as the 500 + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + 500 + ); // The new key as the 500 }); } @@ -3337,8 +3343,7 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { register_ok_neuron(1, hotkey, current_coldkey, 0); // Add balance to the current coldkey without staking - let initial_balance = 500; - Balances::make_free_balance_be(¤t_coldkey, initial_balance); + Balances::make_free_balance_be(¤t_coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP); // Print initial balances log::info!( @@ -3355,7 +3360,10 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { ); // Ensure initial balances are correct - assert_eq!(Balances::total_balance(¤t_coldkey), initial_balance); + assert_eq!( + Balances::total_balance(¤t_coldkey), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); assert_eq!(Balances::total_balance(&hotkey), 0); assert_eq!(Balances::total_balance(&new_coldkey), 0); @@ -3390,7 +3398,10 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { ); // Check that the balance has been transferred to the new coldkey - assert_eq!(Balances::total_balance(&new_coldkey), initial_balance); + assert_eq!( + Balances::total_balance(&new_coldkey), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); assert_eq!(Balances::total_balance(¤t_coldkey), 0); }); } @@ -3402,6 +3413,10 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); SubtensorModule::set_target_stakes_per_interval(10); + SubtensorModule::add_balance_to_coldkey_account( + ¤t_coldkey, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); // Add more stake assert_ok!(SubtensorModule::add_stake( @@ -3435,7 +3450,10 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { ); // Check that the full balance has been transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), 200); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + 200 + ); // Check that the full balance has been transferred to the new coldkey assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); @@ -3454,7 +3472,10 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // Setup network state. add_network(1, 0, 0); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); ArbitrationPeriod::::put(1); // One block arbitration period. register_ok_neuron(1, hotkey, coldkey, 0); @@ -3560,7 +3581,10 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // New key gets amount the other keys are empty. assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey), 0); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey1), 1000); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey1), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 0); }); } @@ -3577,6 +3601,11 @@ fn test_arbitrated_coldkey_swap_existing_destination() { let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, 2_000_000); let (work3, nonce3) = generate_valid_pow(¤t_coldkey, current_block, 4_000_000); + SubtensorModule::add_balance_to_coldkey_account( + ¤t_coldkey, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); + // Schedule a swap to new_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, @@ -3631,6 +3660,10 @@ fn test_arbitration_period_extension() { let current_block = SubtensorModule::get_current_block_as_u64(); let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, 2_000_000); + SubtensorModule::add_balance_to_coldkey_account( + ¤t_coldkey, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); // Schedule a swap to new_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( @@ -3683,8 +3716,14 @@ fn test_concurrent_arbitrated_coldkey_swaps() { register_ok_neuron(netuid2, hotkey2, coldkey2, 0); // Add balance to coldkeys - SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 1000); - SubtensorModule::add_balance_to_coldkey_account(&coldkey2, 1000); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey1, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey2, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); let current_block = SubtensorModule::get_current_block_as_u64(); let (work1, nonce1) = generate_valid_pow(&coldkey1, current_block, 1_000_000); @@ -3713,8 +3752,14 @@ fn test_concurrent_arbitrated_coldkey_swaps() { SubtensorModule::swap_coldkeys_this_block().unwrap(); // Check that the balances have been transferred correctly - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey1), 1000); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 1000); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey1), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey2), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); }); } @@ -3727,6 +3772,11 @@ fn test_get_remaining_arbitration_period() { let current_block = SubtensorModule::get_current_block_as_u64(); let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); + // Schedule a coldkey swap to set the arbitration block assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey_account_id.clone(), @@ -3770,7 +3820,10 @@ fn test_transfer_coldkey_in_arbitration() { let new_coldkey_account_id = U256::from(3); // Add balance to coldkey - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); let current_block = SubtensorModule::get_current_block_as_u64(); let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); @@ -3809,7 +3862,10 @@ fn test_add_stake_coldkey_in_arbitration() { add_network(netuid, tempo, 0); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); let current_block = SubtensorModule::get_current_block_as_u64(); let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); @@ -3844,7 +3900,10 @@ fn test_remove_stake_coldkey_in_arbitration() { add_network(netuid, tempo, 0); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); let current_block = SubtensorModule::get_current_block_as_u64(); From be043acc273ada5192d055a585ca8e54c720050d Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 8 Jul 2024 11:30:08 +0800 Subject: [PATCH 101/185] update runtime version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e6ff7aa81..0afaec385 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 160, + spec_version: 161, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 25744e47abfc1be016046fc916469c46dd8d25ce Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 8 Jul 2024 13:10:21 +0800 Subject: [PATCH 102/185] fix benchmark function --- pallets/subtensor/src/benchmarks.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index cb5bbff8e..6da83c5af 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -437,6 +437,8 @@ reveal_weights { let netuid = 1u16; let tempo = 1u16; + let block_number: u64 = Subtensor::::get_current_block_as_u64(); + let nonce = 0; // Initialize the network Subtensor::::init_new_network(netuid, tempo); @@ -452,5 +454,5 @@ reveal_weights { hotkey.clone() )); - }: schedule_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey) + }: schedule_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone(), vec![], block_number, nonce) } From 8f8b92c170667a02f21b118189cf6995b94082ac Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 8 Jul 2024 13:48:06 +0800 Subject: [PATCH 103/185] add arithmetic side effects --- pallets/subtensor/src/swap.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 7a0cf1eae..cf1a45633 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -293,6 +293,7 @@ impl Pallet { } /// Calculate the proof of work difficulty based on the number of swap attempts + #[allow(clippy::arithmetic_side_effects)] fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { let base_difficulty: U256 = U256::from(1_000_000); // Base difficulty base_difficulty * U256::from(2).pow(U256::from(swap_attempts)) From 6daad1e0c5e0973453aff91ff49a40206458410c Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 8 Jul 2024 13:59:41 +0800 Subject: [PATCH 104/185] fix clippy in test file --- pallets/subtensor/tests/staking.rs | 6 +++--- pallets/subtensor/tests/swap.rs | 29 ----------------------------- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index cb143d438..4c543f586 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3839,7 +3839,7 @@ fn test_transfer_coldkey_in_arbitration() { // Try to transfer balance let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: recipient_account_id.into(), + dest: recipient_account_id, value: 1000, }); @@ -3937,7 +3937,7 @@ fn test_transfer_coldkey_not_in_arbitration() { SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: recipient_account_id.into(), + dest: recipient_account_id, value: 1000, }); @@ -3958,6 +3958,6 @@ fn generate_valid_pow(coldkey: &AccountId, block_number: u64, difficulty: u32) - if SubtensorModule::hash_meets_difficulty(&work, difficulty.into()) { return (work.as_bytes().to_vec(), nonce); } - nonce += 1; + nonce = nonce.saturating_add(1); } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index d6a76ed98..80f46292b 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1253,35 +1253,6 @@ fn test_swap_stake_for_coldkey() { }); } -#[test] -fn test_swap_owner_for_coldkey() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey1 = U256::from(3); - let hotkey2 = U256::from(4); - let mut weight = Weight::zero(); - - // Initialize Owner for old_coldkey - Owner::::insert(hotkey1, old_coldkey); - Owner::::insert(hotkey2, old_coldkey); - - // Initialize OwnedHotkeys map - OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - - // Perform the swap - SubtensorModule::swap_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); - - // Verify the swap - assert_eq!(Owner::::get(hotkey1), new_coldkey); - assert_eq!(Owner::::get(hotkey2), new_coldkey); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} - #[test] fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { new_test_ext(1).execute_with(|| { From fd980b1d04ed3ffb005f9820d7edbb2fe9cd695b Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 11:48:26 +0400 Subject: [PATCH 105/185] chore: add event --- pallets/subtensor/src/events.rs | 9 +++++++++ pallets/subtensor/src/swap.rs | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/events.rs b/pallets/subtensor/src/events.rs index d401eebe1..913072090 100644 --- a/pallets/subtensor/src/events.rs +++ b/pallets/subtensor/src/events.rs @@ -150,5 +150,14 @@ mod events { ::AccountId, >>::Balance, }, + /// A coldkey swap has been scheduled + ColdkeySwapScheduled { + /// The account ID of the old coldkey + old_coldkey: T::AccountId, + /// The account ID of the new coldkey + new_coldkey: T::AccountId, + /// The arbitration block for the coldkey swap + arbitration_block: u64, + }, } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index cf1a45633..7bfcfb9e3 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -204,7 +204,7 @@ impl Pallet { /// /// # Arguments /// - /// * `origin` - The origin of the call, which must be signed by the old coldkey. + /// * `old_coldkey` - The account ID of the old coldkey. /// * `new_coldkey` - The account ID of the new coldkey. /// * `work` - The proof of work submitted by the caller. /// * `block_number` - The block number at which the work was performed. @@ -289,6 +289,13 @@ impl Pallet { ColdkeysToSwapAtBlock::::insert(arbitration_block, key_to_arbitrate_on_this_block); } + // Emit an event indicating that a coldkey swap has been scheduled + Self::deposit_event(Event::ColdkeySwapScheduled { + old_coldkey: old_coldkey.clone(), + new_coldkey: new_coldkey.clone(), + arbitration_block: ColdkeyArbitrationBlock::::get(old_coldkey), + }); + Ok(()) } From fbe19241ecffeecb9ad262859db208d99df830c5 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 19:26:24 +0400 Subject: [PATCH 106/185] feat: unlimited arb time --- pallets/subtensor/src/errors.rs | 2 +- pallets/subtensor/src/events.rs | 5 +++++ pallets/subtensor/src/swap.rs | 14 ++++++++------ pallets/subtensor/tests/staking.rs | 6 +++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index 3bfae3cef..07a16b048 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -151,7 +151,7 @@ mod errors { /// The new coldkey is already registered for the drain DuplicateColdkey, /// Error thrown on a coldkey swap. - SwapError, + ColdkeySwapError, /// Insufficient Balance to Schedule coldkey swap InsufficientBalanceToPerformColdkeySwap, } diff --git a/pallets/subtensor/src/events.rs b/pallets/subtensor/src/events.rs index 913072090..2ed0d621d 100644 --- a/pallets/subtensor/src/events.rs +++ b/pallets/subtensor/src/events.rs @@ -159,5 +159,10 @@ mod events { /// The arbitration block for the coldkey swap arbitration_block: u64, }, + /// The arbitration period has been extended + ArbitrationPeriodExtended { + /// The account ID of the coldkey + coldkey: T::AccountId, + }, } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 7bfcfb9e3..1c6673969 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -144,7 +144,7 @@ impl Pallet { // Actually do the swap. weight = weight.saturating_add( Self::perform_swap_coldkey(old_coldkey, new_coldkey) - .map_err(|_| Error::::SwapError)?, + .map_err(|_| Error::::ColdkeySwapError)?, ); Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); @@ -335,12 +335,14 @@ impl Pallet { ColdkeySwapDestinations::::remove(&coldkey_i); weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - // If the wallets to swap is > 1 we do nothing. + // If the wallets to swap is > 1 we bump the arbitration period. if destinations_coldkeys.len() > 1 { - // Update the arbitration period but we still have the same wallet to swap to. - let next_arbitrage_period: u64 = - current_block.saturating_add(ArbitrationPeriod::::get()); - ColdkeyArbitrationBlock::::insert(coldkey_i.clone(), next_arbitrage_period); + // Set the arbitration period to u64::MAX until we have a senate vote + ColdkeyArbitrationBlock::::insert(coldkey_i.clone(), u64::MAX); + + Self::deposit_event(Event::ArbitrationPeriodExtended { + coldkey: coldkey_i.clone(), + }); } else if let Some(new_coldkey) = destinations_coldkeys.first() { // ONLY 1 wallet: Get the wallet to swap to. // Perform the swap. diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 4c543f586..21137d77d 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3547,14 +3547,14 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // Arbitrate next block. assert_eq!( pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), - SubtensorModule::get_current_block_as_u64() + 1 + u64::MAX ); // Arbitrate. step_block(1); - // Key is not in arbitration - assert!(!SubtensorModule::coldkey_in_arbitration(&coldkey)); + // Key is in arbitration + assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); // Owner schedules a swap for themselves lets go back into arbitration. let current_block = SubtensorModule::get_current_block_as_u64(); From 2e2b51d9fb9fd9fdd02ef38cbe1211bda1b7cc87 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 19:49:58 +0400 Subject: [PATCH 107/185] fix: broken test --- pallets/subtensor/tests/swap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 80f46292b..9db98e716 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -625,7 +625,7 @@ fn test_swap_stake_weight_update() { SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); // Verify the weight update - let expected_weight = ::DbWeight::get().writes(3); + let expected_weight = ::DbWeight::get().writes(4); assert_eq!(weight, expected_weight); }); } @@ -1059,7 +1059,7 @@ fn test_do_swap_coldkey_success() { let netuid = 1u16; let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; - let free_balance_old = 12345u64; + let free_balance_old = 12345u64 + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; // Setup initial state add_network(netuid, 13, 0); From 82be091111b39f62e8d8abbd1d5851e529dedf2b Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 8 Jul 2024 17:51:48 +0200 Subject: [PATCH 108/185] use on_idle for swap_coldkeys logic --- pallets/subtensor/src/lib.rs | 19 +++++++++++-------- pallets/subtensor/src/swap.rs | 31 +++++++++++++++++++------------ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 9ac7cc2eb..4c0efd78f 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1328,18 +1328,23 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { + fn on_idle(_n: BlockNumberFor, remaining_weight: Weight) -> Weight { + // Unstake and transfer pending coldkeys up to the remaining weight + match Self::swap_coldkeys_this_block(&remaining_weight) { + Ok(weight_used) => weight_used, + Err(e) => { + log::error!("Error while swapping coldkeys: {:?}", e); + Weight::default() + } + } + } + // ---- Called on the initialization of this pallet. (the order of on_finalize calls is determined in the runtime) // // # Args: // * 'n': (BlockNumberFor): // - The number of the block we are initializing. fn on_initialize(_block_number: BlockNumberFor) -> Weight { - // Unstake all and transfer pending coldkeys - let swap_weight = match Self::swap_coldkeys_this_block() { - Ok(weight) => weight, - Err(_) => Weight::from_parts(0, 0), - }; - let block_step_result = Self::block_step(); match block_step_result { Ok(_) => { @@ -1348,7 +1353,6 @@ pub mod pallet { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) - .saturating_add(swap_weight) } Err(e) => { // --- If the block step was unsuccessful, return the weight anyway. @@ -1356,7 +1360,6 @@ pub mod pallet { Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) - .saturating_add(swap_weight) } } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 7bfcfb9e3..d6cea6cde 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -314,9 +314,9 @@ impl Pallet { /// /// # Returns /// - /// * `Weight` - The total weight consumed by the operation. - pub fn swap_coldkeys_this_block() -> Result { - let mut weight = frame_support::weights::Weight::from_parts(0, 0); + /// * `Weight` - The total weight consumed by this operation + pub fn swap_coldkeys_this_block(weight_limit: &Weight) -> Result { + let mut weight_used = frame_support::weights::Weight::from_parts(0, 0); // Get the block number let current_block: u64 = Self::get_current_block_as_u64(); @@ -325,15 +325,24 @@ impl Pallet { // Get the coldkeys to swap here and then remove them. let source_coldkeys: Vec = ColdkeysToSwapAtBlock::::get(current_block); ColdkeysToSwapAtBlock::::remove(current_block); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + weight_used = weight_used.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // Iterate over all keys in swap and call perform_swap_coldkey for each + let mut keys_swapped = 0u64; for coldkey_i in source_coldkeys.iter() { + // Terminate early if we've exhausted the weight limit + // + // We care only about ref_time and not proof_size because we are a solochain. + if weight_used.ref_time() > weight_limit.ref_time() { + log::warn!("Could not finish swapping all coldkeys this block due to weight limit, breaking after swapping {} keys.", keys_swapped); + break; + } + // Get the wallets to swap to for this coldkey. let destinations_coldkeys: Vec = ColdkeySwapDestinations::::get(coldkey_i); ColdkeySwapDestinations::::remove(&coldkey_i); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + weight_used = weight_used.saturating_add(T::DbWeight::get().reads_writes(1, 1)); // If the wallets to swap is > 1 we do nothing. if destinations_coldkeys.len() > 1 { @@ -344,16 +353,14 @@ impl Pallet { } else if let Some(new_coldkey) = destinations_coldkeys.first() { // ONLY 1 wallet: Get the wallet to swap to. // Perform the swap. - if Self::perform_swap_coldkey(coldkey_i, new_coldkey) - .map(|w| weight = weight.saturating_add(w)) - .is_err() - { - return Err("Failed to perform coldkey swap"); - } + Self::perform_swap_coldkey(coldkey_i, new_coldkey).map(|weight| { + weight_used = weight_used.saturating_add(weight); + keys_swapped = keys_swapped.saturating_add(1); + })?; } } - Ok(weight) + Ok(weight_used) } pub fn perform_swap_coldkey( From 247c8615eee059f9bd2474ebae40cb1c4336c6a9 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Mon, 8 Jul 2024 12:36:18 -0400 Subject: [PATCH 109/185] Increase base difficulty to 10M for PoW swaps --- pallets/subtensor/src/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 1c6673969..4c9b3a6d7 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -302,7 +302,7 @@ impl Pallet { /// Calculate the proof of work difficulty based on the number of swap attempts #[allow(clippy::arithmetic_side_effects)] fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { - let base_difficulty: U256 = U256::from(1_000_000); // Base difficulty + let base_difficulty: U256 = U256::from(10_000_000); // Base difficulty base_difficulty * U256::from(2).pow(U256::from(swap_attempts)) } From c6dab93a0aaf560a4750b67156069a46dfb72c28 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 9 Jul 2024 01:02:56 +0800 Subject: [PATCH 110/185] add the unit test back --- pallets/subtensor/tests/swap.rs | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 9db98e716..11164c97e 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1387,24 +1387,24 @@ fn test_coldkey_has_associated_hotkeys() { }); } -// #[test] -// fn test_coldkey_arbitrated_sw() { -// new_test_ext(1).execute_with(|| { -// let coldkey = U256::from(1); -// let hotkey = U256::from(2); -// let netuid = 1u16; - -// // Setup initial state -// add_network(netuid, 13, 0); -// register_ok_neuron(netuid, hotkey, coldkey, 0); - -// // Check if coldkey has associated hotkeys -// assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); - -// // Check for a coldkey without associated hotkeys -// let unassociated_coldkey = U256::from(3); -// assert!(!SubtensorModule::coldkey_has_associated_hotkeys( -// &unassociated_coldkey -// )); -// }); -// } +#[test] +fn test_coldkey_arbitrated_sw() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = 1u16; + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Check if coldkey has associated hotkeys + assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); + + // Check for a coldkey without associated hotkeys + let unassociated_coldkey = U256::from(3); + assert!(!SubtensorModule::coldkey_has_associated_hotkeys( + &unassociated_coldkey + )); + }); +} From e271f37b1d841163f5277e0c0b4dea97e831a250 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 21:09:18 +0400 Subject: [PATCH 111/185] fix broken tests --- pallets/subtensor/src/swap.rs | 116 ++++++++++++++------- pallets/subtensor/tests/swap.rs | 174 +++++++++++++++----------------- 2 files changed, 165 insertions(+), 125 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 5ab0ad9d2..11f76768c 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -767,7 +767,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - /// Swaps all stakes associated with a coldkey from the old coldkey to the new coldkey. + /// Swaps the stake associated with a coldkey from the old coldkey to the new coldkey. /// /// # Arguments /// @@ -777,50 +777,96 @@ impl Pallet { /// /// # Effects /// - /// * Removes all stakes associated with the old coldkey. - /// * Inserts all stakes for the new coldkey. + /// * Transfers all stakes from the old coldkey to the new coldkey. + /// * Updates the ownership of hotkeys. + /// * Updates the total stake for both old and new coldkeys. /// * Updates the transaction weight. + /// + pub fn swap_stake_for_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, weight: &mut Weight, ) { - // Swap the owners. - let old_owned_hotkeys = OwnedHotkeys::::get(old_coldkey); - for owned_key in old_owned_hotkeys.clone().iter() { - Owner::::insert(owned_key, new_coldkey); - // Find all hotkeys for this coldkey - let hotkeys = OwnedHotkeys::::get(old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); - for hotkey in hotkeys.iter() { - let stake = Stake::::get(&hotkey, old_coldkey); - Stake::::remove(&hotkey, old_coldkey); - Stake::::insert(&hotkey, new_coldkey, stake); - - // Update StakingHotkeys map - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - StakingHotkeys::::insert(new_coldkey.clone(), staking_hotkeys); - - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); - } - OwnedHotkeys::::remove(old_coldkey.clone()); - OwnedHotkeys::::insert(new_coldkey.clone(), old_owned_hotkeys.clone()); - - // Swap all the keys the coldkey is staking too. - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - StakingHotkeys::::remove(old_coldkey.clone()); - for hotkey in staking_hotkeys.iter() { - // Remove the previous stake and re-insert it. - let stake = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); + // Retrieve the list of hotkeys owned by the old coldkey + let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); + + // Initialize the total transferred stake to zero + let mut total_transferred_stake: u64 = 0u64; + + // Log the total stake of old and new coldkeys before the swap + log::info!( + "Before swap - Old coldkey total stake: {}", + TotalColdkeyStake::::get(old_coldkey) + ); + log::info!( + "Before swap - New coldkey total stake: {}", + TotalColdkeyStake::::get(new_coldkey) + ); + + // Iterate over each hotkey owned by the old coldkey + for hotkey in old_owned_hotkeys.iter() { + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::take(hotkey, old_coldkey); + log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey Stake::::insert(hotkey, new_coldkey, stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 3)); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + + // Update the owner of the hotkey to the new coldkey + Owner::::insert(hotkey, new_coldkey); + + // Update the transaction weight + *weight += T::DbWeight::get().reads_writes(2, 2); } - // Add the new staking keys value. - StakingHotkeys::::insert(new_coldkey.clone(), staking_hotkeys.clone()); } - } + // Log the total transferred stake + log::info!("Total transferred stake: {}", total_transferred_stake); + + // Update the total stake for both old and new coldkeys if any stake was transferred + if total_transferred_stake > 0 { + let old_coldkey_stake: u64 = TotalColdkeyStake::::get(old_coldkey); + let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); + + TotalColdkeyStake::::insert(old_coldkey, 0); + TotalColdkeyStake::::insert( + new_coldkey, + new_coldkey_stake.saturating_add(old_coldkey_stake), + ); + + log::info!("Updated old coldkey stake from {} to 0", old_coldkey_stake); + log::info!( + "Updated new coldkey stake from {} to {}", + new_coldkey_stake, + new_coldkey_stake.saturating_add(old_coldkey_stake) + ); + + // Update the transaction weight + *weight += T::DbWeight::get().reads_writes(2, 2); + } + + // Update the list of owned hotkeys for both old and new coldkeys + OwnedHotkeys::::remove(old_coldkey); + OwnedHotkeys::::insert(new_coldkey, old_owned_hotkeys); + *weight += T::DbWeight::get().reads_writes(1, 2); + + // Update the staking hotkeys for both old and new coldkeys + let staking_hotkeys: Vec = StakingHotkeys::::take(old_coldkey); + StakingHotkeys::::insert(new_coldkey, staking_hotkeys); + *weight += T::DbWeight::get().reads_writes(1, 1); + + // Log the total stake of old and new coldkeys after the swap + log::info!( + "After swap - Old coldkey total stake: {}", + TotalColdkeyStake::::get(old_coldkey) + ); + log::info!( + "After swap - New coldkey total stake: {}", + TotalColdkeyStake::::get(new_coldkey) + ); + } /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. /// /// # Arguments diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 11164c97e..c9286d771 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1072,6 +1072,20 @@ fn test_do_swap_coldkey_success() { stake_amount1 + stake_amount2 + free_balance_old, ); + // Log initial state + log::info!( + "Initial total stake: {}", + SubtensorModule::get_total_stake() + ); + log::info!( + "Initial old coldkey stake: {}", + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) + ); + log::info!( + "Initial new coldkey stake: {}", + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) + ); + // Add stake to the neurons assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(old_coldkey), @@ -1084,22 +1098,23 @@ fn test_do_swap_coldkey_success() { stake_amount2 )); - // Verify initial stakes and balances - assert_eq!( - TotalColdkeyStake::::get(old_coldkey), - stake_amount1 + stake_amount2 + // Log state after adding stake + log::info!( + "Total stake after adding: {}", + SubtensorModule::get_total_stake() ); - assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1); - assert_eq!(Stake::::get(hotkey2, old_coldkey), stake_amount2); - assert_eq!( - OwnedHotkeys::::get(old_coldkey), - vec![hotkey1, hotkey2] + log::info!( + "Old coldkey stake after adding: {}", + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&old_coldkey), - free_balance_old + log::info!( + "New coldkey stake after adding: {}", + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) ); + // Record total stake before swap + let total_stake_before_swap = SubtensorModule::get_total_stake(); + // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), @@ -1107,6 +1122,20 @@ fn test_do_swap_coldkey_success() { &new_coldkey )); + // Log state after swap + log::info!( + "Total stake after swap: {}", + SubtensorModule::get_total_stake() + ); + log::info!( + "Old coldkey stake after swap: {}", + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) + ); + log::info!( + "New coldkey stake after swap: {}", + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) + ); + // Verify the swap assert_eq!(Owner::::get(hotkey1), new_coldkey); assert_eq!(Owner::::get(hotkey2), new_coldkey); @@ -1114,7 +1143,7 @@ fn test_do_swap_coldkey_success() { TotalColdkeyStake::::get(new_coldkey), stake_amount1 + stake_amount2 ); - assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); assert!(!Stake::::contains_key(hotkey1, old_coldkey)); @@ -1134,6 +1163,13 @@ fn test_do_swap_coldkey_success() { ); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); + // Verify total stake remains unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + total_stake_before_swap, + "Total stake changed unexpectedly" + ); + // Verify event emission System::assert_last_event( Event::ColdkeySwapped { @@ -1145,31 +1181,6 @@ fn test_do_swap_coldkey_success() { }); } -#[test] -fn test_swap_total_coldkey_stake() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let stake_amount = 1000u64; - let mut weight = Weight::zero(); - - // Initialize TotalColdkeyStake for old_coldkey - TotalColdkeyStake::::insert(old_coldkey, stake_amount); - - // Perform the swap - SubtensorModule::swap_total_coldkey_stake(&old_coldkey, &new_coldkey, &mut weight); - - // Verify the swap - assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake_amount); - assert!(!TotalColdkeyStake::::contains_key(old_coldkey)); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_swap_stake_for_coldkey --exact --nocapture #[test] fn test_swap_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1179,77 +1190,60 @@ fn test_swap_stake_for_coldkey() { let hotkey2 = U256::from(4); let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; + let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount1 + stake_amount2 + 123456, - ); - add_network(1, 13, 0); - register_ok_neuron(1, hotkey1, old_coldkey, 0); - register_ok_neuron(1, hotkey2, old_coldkey, 0); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey1, - stake_amount1 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey2, - stake_amount2 - )); - // Get owned keys - assert_eq!( - SubtensorModule::get_owned_hotkeys(&old_coldkey), - vec![hotkey1, hotkey2] - ); - assert_eq!(SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![]); - // Get all staked keys. - assert_eq!( - SubtensorModule::get_all_staked_hotkeys(&old_coldkey), - vec![hotkey1, hotkey2] - ); - assert_eq!( - SubtensorModule::get_all_staked_hotkeys(&new_coldkey), - vec![] - ); + // Setup initial state + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + Stake::::insert(hotkey1, old_coldkey, stake_amount1); + Stake::::insert(hotkey2, old_coldkey, stake_amount2); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); + TotalHotkeyStake::::insert(hotkey2, stake_amount2); + TotalColdkeyStake::::insert(old_coldkey, total_stake); + + // Set up total issuance + TotalIssuance::::put(total_stake); + TotalStake::::put(total_stake); + + // Record initial values + let initial_total_issuance = SubtensorModule::get_total_issuance(); + let initial_total_stake = SubtensorModule::get_total_stake(); // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); - // Get owned keys + // Verify ownership transfer assert_eq!( SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey1, hotkey2] ); assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); - // Get all staked keys. - assert_eq!( - SubtensorModule::get_all_staked_hotkeys(&new_coldkey), - vec![hotkey1, hotkey2] - ); - assert_eq!( - SubtensorModule::get_all_staked_hotkeys(&old_coldkey), - vec![] - ); - // Verify the swap + // Verify stake transfer assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); - assert!(!Stake::::contains_key(hotkey1, old_coldkey)); - assert!(!Stake::::contains_key(hotkey2, old_coldkey)); + assert_eq!(Stake::::get(hotkey1, old_coldkey), 0); + assert_eq!(Stake::::get(hotkey2, old_coldkey), 0); + + // Verify TotalColdkeyStake + assert_eq!(TotalColdkeyStake::::get(new_coldkey), total_stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); // Verify TotalHotkeyStake remains unchanged assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); - // Verify TotalStake and TotalIssuance remain unchanged - assert_eq!(TotalStake::::get(), stake_amount1 + stake_amount2); - assert_eq!(TotalIssuance::::get(), stake_amount1 + stake_amount2); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(5, 6); - assert_eq!(weight, expected_weight); + // Verify total stake and issuance remain unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + initial_total_stake, + "Total stake changed unexpectedly" + ); + assert_eq!( + SubtensorModule::get_total_issuance(), + initial_total_issuance, + "Total issuance changed unexpectedly" + ); }); } From 54a1466fd0b45e6c9b870beaf52ea6b2aca6fb73 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 22:15:33 +0400 Subject: [PATCH 112/185] chore: fix tests --- pallets/subtensor/src/swap.rs | 2 +- pallets/subtensor/tests/mock.rs | 14 +- pallets/subtensor/tests/staking.rs | 270 ++++++++++++++++------------- pallets/subtensor/tests/swap.rs | 42 ++--- 4 files changed, 177 insertions(+), 151 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 11f76768c..498894f87 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -301,7 +301,7 @@ impl Pallet { /// Calculate the proof of work difficulty based on the number of swap attempts #[allow(clippy::arithmetic_side_effects)] - fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { + pub fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { let base_difficulty: U256 = U256::from(10_000_000); // Base difficulty base_difficulty * U256::from(2).pow(U256::from(swap_attempts)) } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 3e7e74f95..9e21f2a63 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -2,21 +2,22 @@ use frame_support::derive_impl; use frame_support::dispatch::DispatchResultWithPostInfo; use frame_support::weights::constants::RocksDbWeight; +// use frame_support::weights::constants::WEIGHT_PER_SECOND; +use frame_support::weights::Weight; use frame_support::{ assert_ok, parameter_types, traits::{Everything, Hooks}, - weights, }; use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; +use pallet_collective::MemberCount; use sp_core::{Get, H256, U256}; +use sp_runtime::Perbill; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -use pallet_collective::MemberCount; - type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -86,7 +87,7 @@ impl pallet_balances::Config for Test { #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl system::Config for Test { type BaseCallFilter = Everything; - type BlockWeights = (); + type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; @@ -114,7 +115,10 @@ parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; pub const InitialMaxWeightsLimit: u16 = u16::MAX; - pub BlockWeights: limits::BlockWeights = limits::BlockWeights::simple_max(weights::Weight::from_parts(1024, 0)); + pub BlockWeights: limits::BlockWeights = limits::BlockWeights::with_sensible_defaults( + Weight::from_parts(2_000_000_000_000, u64::MAX), + Perbill::from_percent(75), + ); pub const ExistentialDeposit: Balance = 1; pub const TransactionByteFee: Balance = 100; pub const SDebug:u64 = 1; diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index b8b51ff08..c6216b456 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3,6 +3,8 @@ use frame_support::pallet_prelude::{ InvalidTransaction, TransactionValidity, TransactionValidityError, }; +use frame_support::traits::{OnFinalize, OnIdle, OnInitialize}; +use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::Config; mod mock; @@ -3165,7 +3167,8 @@ fn test_arbitrated_coldkey_swap_success() { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); SubtensorModule::add_balance_to_coldkey_account( ¤t_coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, @@ -3173,7 +3176,7 @@ fn test_arbitrated_coldkey_swap_success() { assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); @@ -3236,13 +3239,14 @@ fn test_arbitrated_coldkey_swap_same_coldkey() { let (current_coldkey, _hotkey, _) = setup_test_environment(); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); assert_noop!( SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), ¤t_coldkey, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce ), @@ -3286,13 +3290,14 @@ fn test_arbitrated_coldkey_swap_no_balance() { // Generate valid PoW let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); // Try to schedule coldkey swap let result = SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce, ); @@ -3368,20 +3373,24 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { assert_eq!(Balances::total_balance(&new_coldkey), 0); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); - // Perform unstake and transfer + // Schedule coldkey swap assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); - // Make 7200 * 4 blocks pass + // Make 7200 * 4 blocks pass, simulating on_idle for each block let drain_block: u64 = 7200 * 3 + 1; - run_to_block(drain_block); + for _ in 0..drain_block { + next_block(); + SubtensorModule::on_idle(System::block_number(), Weight::MAX); + } // Print final balances log::info!( @@ -3426,19 +3435,23 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { )); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); - // Make 7200 * 4 blocks pass + // Make 7200 * 4 blocks pass, simulating on_idle for each block let drain_block: u64 = 7200 * 3 + 1; - run_to_block(drain_block); + for _ in 0..drain_block { + next_block(); + SubtensorModule::on_idle(System::block_number(), Weight::MAX); + } // Check that all stake has been removed assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 800); @@ -3459,16 +3472,16 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); }); } - // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_multiple_arbitrations --exact --nocapture #[test] fn test_arbitrated_coldkey_swap_multiple_arbitrations() { new_test_ext(1).execute_with(|| { - // Create coldkey with two choices. + // Create coldkey with three choices. let coldkey: AccountId = U256::from(1); let new_coldkey1: AccountId = U256::from(2); let new_coldkey2: AccountId = U256::from(3); - let hotkey: AccountId = U256::from(4); + let new_coldkey3: AccountId = U256::from(4); + let hotkey: AccountId = U256::from(5); // Setup network state. add_network(1, 0, 0); @@ -3476,116 +3489,94 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { &coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, ); - ArbitrationPeriod::::put(1); // One block arbitration period. + ArbitrationPeriod::::put(5); // Set arbitration period to 5 blocks register_ok_neuron(1, hotkey, coldkey, 0); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow(&coldkey, current_block, 1_000_000); - let (work2, nonce2) = generate_valid_pow(&coldkey, current_block, 2_000_000); - - // Owner schedules a swap for themselves. + let (work1, nonce1) = + generate_valid_pow(&coldkey, current_block, U256::from(10_000_000u64)); + let (work2, nonce2) = + generate_valid_pow(&coldkey, current_block, U256::from(20_000_000u64)); + let (work3, nonce3) = + generate_valid_pow(&coldkey, current_block, U256::from(30_000_000u64)); + + // Schedule three swaps assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey.clone(), &new_coldkey1, - work1, + work1.to_fixed_bytes().to_vec(), current_block, nonce1 )); - - // Attacker schedules the second swap for themselves. assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey.clone(), &new_coldkey2, - work2, + work2.to_fixed_bytes().to_vec(), current_block, nonce2 )); + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &coldkey.clone(), + &new_coldkey3, + work3.to_fixed_bytes().to_vec(), + current_block, + nonce3 + )); - // Both keys are added in swap destinations. + // All three keys are added in swap destinations. assert_eq!( pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), - vec![new_coldkey1, new_coldkey2] + vec![new_coldkey1, new_coldkey2, new_coldkey3] ); - // Check that we are arbitrating next block. - assert_eq!( - pallet_subtensor::ColdkeysToSwapAtBlock::::get( - SubtensorModule::get_current_block_as_u64() + 1 - ), - vec![coldkey] - ); + // Simulate the passage of blocks and on_idle calls + for i in 0..(7200 * 3 + 1) { + // Simulate 10 blocks + next_block(); + SubtensorModule::on_idle(System::block_number(), Weight::MAX); - // Key is in arbitration. - assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); + log::info!( + "Block {}: Coldkey in arbitration: {}, Swap destinations: {:?}", + i + 1, + SubtensorModule::coldkey_in_arbitration(&coldkey), + pallet_subtensor::ColdkeySwapDestinations::::get(coldkey) + ); + } - // Arbitrate next block. + // Check that the swap destinations remain unchanged due to multiple (>2) swap calls assert_eq!( - pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), - SubtensorModule::get_current_block_as_u64() + 1 + pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), + vec![new_coldkey1, new_coldkey2, new_coldkey3], + "ColdkeySwapDestinations should remain unchanged with more than two swap calls" ); - // Arbitrate. - step_block(1); - - // Both keys are removed and a new period begins. - assert_eq!( - pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), - vec![] + // Key remains in arbitration due to multiple (>2) swap calls + assert!( + SubtensorModule::coldkey_in_arbitration(&coldkey), + "Coldkey should remain in arbitration with more than two swap calls" ); - // Arbitration has been pushed back but there are no keys to add to the list to arbitrate. + // Check that no balance has been transferred assert_eq!( - pallet_subtensor::ColdkeysToSwapAtBlock::::get( - SubtensorModule::get_current_block_as_u64() + 1 - ), - vec![] + SubtensorModule::get_coldkey_balance(&coldkey), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + "Original coldkey balance should remain unchanged" ); - - // Key is in arbitration. - assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); - - // Arbitrate next block. assert_eq!( - pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), - u64::MAX + SubtensorModule::get_coldkey_balance(&new_coldkey1), + 0, + "New coldkey1 should not receive any balance" ); - - // Arbitrate. - step_block(1); - - // Key is in arbitration - assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); - - // Owner schedules a swap for themselves lets go back into arbitration. - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work3, nonce3) = generate_valid_pow(&coldkey, current_block, 4_000_000); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey.clone(), - &new_coldkey1, - work3, - current_block, - nonce3 - )); - - // Key goes back into arbitration. - assert!(SubtensorModule::coldkey_in_arbitration(&coldkey)); - - // Arbitrate next block. assert_eq!( - pallet_subtensor::ColdkeyArbitrationBlock::::get(coldkey), - SubtensorModule::get_current_block_as_u64() + 1 + SubtensorModule::get_coldkey_balance(&new_coldkey2), + 0, + "New coldkey2 should not receive any balance" ); - - // Arbitrate. - step_block(1); - - // New key gets amount the other keys are empty. - assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey), 0); assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey1), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + SubtensorModule::get_coldkey_balance(&new_coldkey3), + 0, + "New coldkey3 should not receive any balance" ); - assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey2), 0); }); } @@ -3595,54 +3586,63 @@ fn test_arbitrated_coldkey_swap_existing_destination() { new_test_ext(1).execute_with(|| { let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); let another_coldkey = U256::from(4); + let third_coldkey = U256::from(5); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); - let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, 2_000_000); - let (work3, nonce3) = generate_valid_pow(¤t_coldkey, current_block, 4_000_000); SubtensorModule::add_balance_to_coldkey_account( ¤t_coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, ); + // First swap attempt (0 existing destinations) + let difficulty1 = SubtensorModule::calculate_pow_difficulty(0); + let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, difficulty1); + // Schedule a swap to new_coldkey assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey, &new_coldkey, - work1, + work1.to_fixed_bytes().to_vec(), current_block, nonce1 )); + // Second swap attempt (1 existing destination) + let difficulty2 = SubtensorModule::calculate_pow_difficulty(1); + let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, difficulty2); + // Attempt to schedule a swap to the same new_coldkey again assert_noop!( SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, - work2.clone(), + work2.to_fixed_bytes().to_vec(), current_block, nonce2 ), Error::::DuplicateColdkey ); - // Schedule a swap to another_coldkey + // Schedule a swap to another_coldkey (still 1 existing destination) assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &another_coldkey, - work2, + work2.to_fixed_bytes().to_vec(), current_block, nonce2 )); + // Third swap attempt (2 existing destinations) + let difficulty3 = SubtensorModule::calculate_pow_difficulty(2); + let (work3, nonce3) = generate_valid_pow(¤t_coldkey, current_block, difficulty3); + // Attempt to schedule a third swap - let third_coldkey = U256::from(5); assert_noop!( SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &third_coldkey, - work3, + work3.to_fixed_bytes().to_vec(), current_block, nonce3 ), @@ -3658,8 +3658,10 @@ fn test_arbitration_period_extension() { let another_coldkey = U256::from(4); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, 1_000_000); - let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, 2_000_000); + let (work1, nonce1) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work2, nonce2) = + generate_valid_pow(¤t_coldkey, current_block, U256::from(20_000_000u64)); SubtensorModule::add_balance_to_coldkey_account( ¤t_coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, @@ -3669,7 +3671,7 @@ fn test_arbitration_period_extension() { assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &new_coldkey, - work1, + work1.to_fixed_bytes().to_vec(), current_block, nonce1 )); @@ -3678,7 +3680,7 @@ fn test_arbitration_period_extension() { assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), &another_coldkey, - work2, + work2.to_fixed_bytes().to_vec(), current_block, nonce2 )); @@ -3726,25 +3728,26 @@ fn test_concurrent_arbitrated_coldkey_swaps() { ); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow(&coldkey1, current_block, 1_000_000); - let (work2, nonce2) = generate_valid_pow(&coldkey2, current_block, 1_000_000); - + let (work1, nonce1) = + generate_valid_pow(&coldkey1, current_block, U256::from(10_000_000u64)); + let (work2, nonce2) = + generate_valid_pow(&coldkey2, current_block, U256::from(10_000_000u64)); // Schedule swaps for both coldkeys assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey1.clone(), &new_coldkey1, - work1, + work1.to_fixed_bytes().to_vec(), current_block, nonce1 )); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey2.clone(), &new_coldkey2, - work2, + work2.to_fixed_bytes().to_vec(), current_block, nonce2 )); - // Make 7200 * 4 blocks pass + // Make 7200 * 3 blocks pass let drain_block: u64 = 7200 * 3 + 1; run_to_block(drain_block); @@ -3770,7 +3773,11 @@ fn test_get_remaining_arbitration_period() { let new_coldkey_account_id = U256::from(54321); // arbitrary new coldkey let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + let (work, nonce) = generate_valid_pow( + &coldkey_account_id, + current_block, + U256::from(10_000_000u64), + ); SubtensorModule::add_balance_to_coldkey_account( &coldkey_account_id, @@ -3781,7 +3788,7 @@ fn test_get_remaining_arbitration_period() { assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey_account_id.clone(), &new_coldkey_account_id, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); @@ -3826,13 +3833,17 @@ fn test_transfer_coldkey_in_arbitration() { ); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + let (work, nonce) = generate_valid_pow( + &coldkey_account_id, + current_block, + U256::from(10_000_000u64), + ); // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey_account_id.clone(), &new_coldkey_account_id, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); @@ -3868,13 +3879,14 @@ fn test_add_stake_coldkey_in_arbitration() { ); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(&coldkey_account_id, current_block, U256::from(1_000_000u64)); // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey_account_id.clone(), &new_coldkey_account_id, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); @@ -3907,13 +3919,14 @@ fn test_remove_stake_coldkey_in_arbitration() { SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow(&coldkey_account_id, current_block, 1_000_000); + let (work, nonce) = + generate_valid_pow(&coldkey_account_id, current_block, U256::from(1_000_000u64)); // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey_account_id.clone(), &new_coldkey_account_id, - work, + work.to_fixed_bytes().to_vec(), current_block, nonce )); @@ -3951,13 +3964,22 @@ fn validate_transaction(who: &AccountId, call: &RuntimeCall) -> TransactionValid } // Helper function to generate valid PoW -fn generate_valid_pow(coldkey: &AccountId, block_number: u64, difficulty: u32) -> (Vec, u64) { +fn generate_valid_pow(coldkey: &U256, block_number: u64, difficulty: U256) -> (H256, u64) { let mut nonce: u64 = 0; loop { let work = SubtensorModule::create_seal_hash(block_number, nonce, coldkey); - if SubtensorModule::hash_meets_difficulty(&work, difficulty.into()) { - return (work.as_bytes().to_vec(), nonce); + if SubtensorModule::hash_meets_difficulty(&work, difficulty) { + return (work, nonce); } - nonce = nonce.saturating_add(1); + nonce += 1; } } + +// Helper function to advance to the next block and run hooks +fn next_block() { + let current_block = System::block_number(); + System::on_finalize(current_block); + System::set_block_number(current_block + 1); + System::on_initialize(System::block_number()); + SubtensorModule::on_initialize(System::block_number()); +} diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index c9286d771..d698e4385 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1381,24 +1381,24 @@ fn test_coldkey_has_associated_hotkeys() { }); } -#[test] -fn test_coldkey_arbitrated_sw() { - new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1); - let hotkey = U256::from(2); - let netuid = 1u16; - - // Setup initial state - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, coldkey, 0); - - // Check if coldkey has associated hotkeys - assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); - - // Check for a coldkey without associated hotkeys - let unassociated_coldkey = U256::from(3); - assert!(!SubtensorModule::coldkey_has_associated_hotkeys( - &unassociated_coldkey - )); - }); -} +// #[test] +// fn test_coldkey_arbitrated_sw() { +// new_test_ext(1).execute_with(|| { +// let coldkey = U256::from(1); +// let hotkey = U256::from(2); +// let netuid = 1u16; + +// // Setup initial state +// add_network(netuid, 13, 0); +// register_ok_neuron(netuid, hotkey, coldkey, 0); + +// // Check if coldkey has associated hotkeys +// assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); + +// // Check for a coldkey without associated hotkeys +// let unassociated_coldkey = U256::from(3); +// assert!(!SubtensorModule::coldkey_has_associated_hotkeys( +// &unassociated_coldkey +// )); +// }); +// } From 7bf65241c189d037c5b19c8a2d13f2711a2a9e51 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 23:06:15 +0400 Subject: [PATCH 113/185] chore: fix test_arbitrated_coldkey_swap_multiple_arbitrations --- pallets/subtensor/src/errors.rs | 2 ++ pallets/subtensor/src/swap.rs | 37 ++++++++++++++------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/errors.rs index 07a16b048..100be89b7 100644 --- a/pallets/subtensor/src/errors.rs +++ b/pallets/subtensor/src/errors.rs @@ -154,5 +154,7 @@ mod errors { ColdkeySwapError, /// Insufficient Balance to Schedule coldkey swap InsufficientBalanceToPerformColdkeySwap, + /// The maximum number of coldkey destinations has been reached + MaxColdkeyDestinationsReached, } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 498894f87..d7a0335e2 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -265,12 +265,14 @@ impl Pallet { Error::::DuplicateColdkey ); - // If the destinations keys are empty or have size 1 then we will add the new coldkey to the list - if destination_coldkeys.is_empty() || destination_coldkeys.len() == 1_usize { + // If the destinations keys are empty or have less than the maximum allowed, we will add the new coldkey to the list + const MAX_COLDKEY_DESTINATIONS: usize = 10; + + if destination_coldkeys.len() < MAX_COLDKEY_DESTINATIONS { destination_coldkeys.push(new_coldkey.clone()); ColdkeySwapDestinations::::insert(old_coldkey.clone(), destination_coldkeys.clone()); } else { - return Err(Error::::ColdkeyIsInArbitration.into()); + return Err(Error::::MaxColdkeyDestinationsReached.into()); } // It is the first time we have seen this key @@ -318,43 +320,36 @@ impl Pallet { pub fn swap_coldkeys_this_block(weight_limit: &Weight) -> Result { let mut weight_used = frame_support::weights::Weight::from_parts(0, 0); - // Get the block number let current_block: u64 = Self::get_current_block_as_u64(); log::debug!("Swapping coldkeys for block: {:?}", current_block); - // Get the coldkeys to swap here and then remove them. let source_coldkeys: Vec = ColdkeysToSwapAtBlock::::get(current_block); ColdkeysToSwapAtBlock::::remove(current_block); weight_used = weight_used.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - // Iterate over all keys in swap and call perform_swap_coldkey for each let mut keys_swapped = 0u64; for coldkey_i in source_coldkeys.iter() { - // Terminate early if we've exhausted the weight limit - // - // We care only about ref_time and not proof_size because we are a solochain. - if weight_used.ref_time() > weight_limit.ref_time() { - log::warn!("Could not finish swapping all coldkeys this block due to weight limit, breaking after swapping {} keys.", keys_swapped); - break; - } + // TODO: need a sane way to terminate early without locking users in. + // we should update the swap time + // if weight_used.ref_time() > weight_limit.ref_time() { + // log::warn!("Could not finish swapping all coldkeys this block due to weight limit, breaking after swapping {} keys.", keys_swapped); + // break; + // } - // Get the wallets to swap to for this coldkey. let destinations_coldkeys: Vec = ColdkeySwapDestinations::::get(coldkey_i); - ColdkeySwapDestinations::::remove(&coldkey_i); - weight_used = weight_used.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + weight_used = weight_used.saturating_add(T::DbWeight::get().reads(1)); - // If the wallets to swap is > 1 we bump the arbitration period. if destinations_coldkeys.len() > 1 { - // Set the arbitration period to u64::MAX until we have a senate vote + // Do not remove ColdkeySwapDestinations if there are multiple destinations ColdkeyArbitrationBlock::::insert(coldkey_i.clone(), u64::MAX); - Self::deposit_event(Event::ArbitrationPeriodExtended { coldkey: coldkey_i.clone(), }); } else if let Some(new_coldkey) = destinations_coldkeys.first() { - // ONLY 1 wallet: Get the wallet to swap to. - // Perform the swap. + // Only remove ColdkeySwapDestinations if there's a single destination + ColdkeySwapDestinations::::remove(&coldkey_i); + weight_used = weight_used.saturating_add(T::DbWeight::get().writes(1)); Self::perform_swap_coldkey(coldkey_i, new_coldkey).map(|weight| { weight_used = weight_used.saturating_add(weight); keys_swapped = keys_swapped.saturating_add(1); From 2891d621a59f0076fef47fc54a832c4a6d45a866 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 8 Jul 2024 23:27:25 +0400 Subject: [PATCH 114/185] chore: fix tests --- pallets/subtensor/src/swap.rs | 4 ++-- pallets/subtensor/tests/staking.rs | 17 +++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index d7a0335e2..34707ebea 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -305,7 +305,7 @@ impl Pallet { #[allow(clippy::arithmetic_side_effects)] pub fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { let base_difficulty: U256 = U256::from(10_000_000); // Base difficulty - base_difficulty * U256::from(2).pow(U256::from(swap_attempts)) + base_difficulty.saturating_mul(U256::from(2).pow(U256::from(swap_attempts))) } /// Arbitrates coldkeys that are scheduled to be swapped on this block. @@ -317,7 +317,7 @@ impl Pallet { /// # Returns /// /// * `Weight` - The total weight consumed by this operation - pub fn swap_coldkeys_this_block(weight_limit: &Weight) -> Result { + pub fn swap_coldkeys_this_block(_weight_limit: &Weight) -> Result { let mut weight_used = frame_support::weights::Weight::from_parts(0, 0); let current_block: u64 = Self::get_current_block_as_u64(); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index c6216b456..caf6c75eb 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3638,16 +3638,13 @@ fn test_arbitrated_coldkey_swap_existing_destination() { let (work3, nonce3) = generate_valid_pow(¤t_coldkey, current_block, difficulty3); // Attempt to schedule a third swap - assert_noop!( - SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &third_coldkey, - work3.to_fixed_bytes().to_vec(), - current_block, - nonce3 - ), - Error::::ColdkeyIsInArbitration - ); + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + ¤t_coldkey.clone(), + &third_coldkey, + work3.to_fixed_bytes().to_vec(), + current_block, + nonce3 + )); }); } From d1ee74444b52f24b43ecb4505b933d2f8f5943e7 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 8 Jul 2024 14:44:30 -0500 Subject: [PATCH 115/185] add swap test --- pallets/subtensor/src/swap.rs | 3 +- pallets/subtensor/tests/swap.rs | 129 ++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index d7a0335e2..48641b2dc 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -374,7 +374,6 @@ impl Pallet { // Swap coldkey references in storage maps // NOTE The order of these calls is important - Self::swap_total_coldkey_stake(old_coldkey, new_coldkey, &mut weight); Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); Self::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( old_coldkey, @@ -822,7 +821,7 @@ impl Pallet { // Update the total stake for both old and new coldkeys if any stake was transferred if total_transferred_stake > 0 { - let old_coldkey_stake: u64 = TotalColdkeyStake::::get(old_coldkey); + let old_coldkey_stake: u64 = TotalColdkeyStake::::take(old_coldkey); // Remove it here. let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); TotalColdkeyStake::::insert(old_coldkey, 0); diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index d698e4385..e680c849e 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1381,6 +1381,135 @@ fn test_coldkey_has_associated_hotkeys() { }); } + + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_swap_total --exact --nocapture +#[test] +fn test_coldkey_swap_total() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let nominator1 = U256::from(2); + let nominator2 = U256::from(3); + let nominator3 = U256::from(4); + let delegate1 = U256::from(5); + let delegate2 = U256::from(6); + let delegate3 = U256::from(7); + let hotkey1 = U256::from(2); + let hotkey2 = U256::from(3); + let hotkey3 = U256::from(4); + let netuid1 = 1u16; + let netuid2 = 2u16; + let netuid3 = 3u16; + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&delegate1, 1000); + SubtensorModule::add_balance_to_coldkey_account(&delegate2, 1000); + SubtensorModule::add_balance_to_coldkey_account(&delegate3, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator1, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator2, 1000); + SubtensorModule::add_balance_to_coldkey_account(&nominator3, 1000); + + // Setup initial state + add_network(netuid1, 13, 0); + add_network(netuid2, 14, 0); + add_network(netuid3, 15, 0); + register_ok_neuron(netuid1, hotkey1, coldkey, 0); + register_ok_neuron(netuid2, hotkey2, coldkey, 0); + register_ok_neuron(netuid3, hotkey3, coldkey, 0); + register_ok_neuron(netuid1, delegate1, delegate1, 0); + register_ok_neuron(netuid2, delegate2, delegate2, 0); + register_ok_neuron(netuid3, delegate3, delegate3, 0); + assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(coldkey), hotkey1, u16::MAX / 10)); + assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(coldkey), hotkey2, u16::MAX / 10)); + assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(coldkey), hotkey3, u16::MAX / 10)); + assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(delegate1), delegate1, u16::MAX / 10)); + assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(delegate2), delegate2, u16::MAX / 10)); + assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(delegate3), delegate3, u16::MAX / 10)); + + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), hotkey1, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), hotkey2, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), hotkey3, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), delegate1, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), delegate2, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), delegate3, 100 )); + + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate1), hotkey1, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate2), hotkey2, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate3), hotkey3, 100 )); + + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate1), delegate1, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate2), delegate2, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate3), delegate3, 100 )); + + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator1), hotkey1, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator2), hotkey2, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator3), hotkey3, 100 )); + + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator1), delegate1, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator2), delegate2, 100 )); + assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator3), delegate3, 100 )); + + assert_eq!( SubtensorModule::get_owned_hotkeys(&coldkey), vec![hotkey1, hotkey2, hotkey3] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&coldkey), vec![hotkey1, hotkey2, hotkey3, delegate1, delegate2, delegate3] ); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate1), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate2), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate3), 300 ); + + + assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate1), vec![delegate1] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate2), vec![delegate2] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate3), vec![delegate3] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate1), vec![delegate1, hotkey1] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate2), vec![delegate2, hotkey2] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate3), vec![delegate3, hotkey3] ); + + assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator1), vec![] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator2), vec![] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator3), vec![] ); + + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator1), vec![hotkey1, delegate1] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator2), vec![hotkey2, delegate2] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator3), vec![hotkey3, delegate3] ); + + // Perform the swap + let new_coldkey = U256::from(1100); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600 ); + assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, &new_coldkey )); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), 600 ); + + // Check everything is swapped. + assert_eq!( SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey1, hotkey2, hotkey3] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![hotkey1, hotkey2, hotkey3, delegate1, delegate2, delegate3] ); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), 600 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate1), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate2), 300 ); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate3), 300 ); + + assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate1), vec![delegate1] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate2), vec![delegate2] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate3), vec![delegate3] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate1), vec![delegate1, hotkey1] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate2), vec![delegate2, hotkey2] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate3), vec![delegate3, hotkey3] ); + + assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator1), vec![] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator2), vec![] ); + assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator3), vec![] ); + + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator1), vec![hotkey1, delegate1] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator2), vec![hotkey2, delegate2] ); + assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator3), vec![hotkey3, delegate3] ); + + + }); +} + // #[test] // fn test_coldkey_arbitrated_sw() { // new_test_ext(1).execute_with(|| { From 4a97e7953047b0cb319a5244fa5408162e850c93 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 8 Jul 2024 14:56:34 -0500 Subject: [PATCH 116/185] working --- pallets/subtensor/src/lib.rs | 3 +-- pallets/subtensor/src/swap.rs | 7 +++---- pallets/subtensor/tests/swap.rs | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 4c0efd78f..d57445824 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2059,10 +2059,9 @@ pub mod pallet { .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] pub fn swap_coldkey( origin: OriginFor, - old_coldkey: T::AccountId, new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { - Self::do_swap_coldkey(origin, &old_coldkey, &new_coldkey) + Self::do_swap_coldkey(origin, &new_coldkey) } /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index b56e6f4ea..07cde1d69 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -119,12 +119,11 @@ impl Pallet { /// Weight is tracked and updated throughout the function execution. pub fn do_swap_coldkey( origin: T::RuntimeOrigin, - old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { - let coldkey_performing_swap = ensure_signed(origin)?; + let old_coldkey = ensure_signed(origin)?; ensure!( - !Self::coldkey_in_arbitration(&coldkey_performing_swap), + !Self::coldkey_in_arbitration(&old_coldkey), Error::::ColdkeyIsInArbitration ); @@ -143,7 +142,7 @@ impl Pallet { // Actually do the swap. weight = weight.saturating_add( - Self::perform_swap_coldkey(old_coldkey, new_coldkey) + Self::perform_swap_coldkey(&old_coldkey, new_coldkey) .map_err(|_| Error::::ColdkeySwapError)?, ); diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index e680c849e..a389b5bd9 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1118,7 +1118,6 @@ fn test_do_swap_coldkey_success() { // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, &new_coldkey )); @@ -1350,7 +1349,6 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, &new_coldkey )); From ba00c231f6ef4dc82407e951cf9a3f1c6d9eac83 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 8 Jul 2024 15:23:45 -0500 Subject: [PATCH 117/185] add stake balance as equal --- pallets/subtensor/src/swap.rs | 12 +++++++++- pallets/subtensor/tests/staking.rs | 38 ++++++++++++++++++++++++++++++ pallets/subtensor/tests/swap.rs | 10 +------- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 07cde1d69..b65e5cc43 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -199,6 +199,16 @@ impl Pallet { } } + pub fn meets_min_allowed_coldkey_balance( coldkey: &T::AccountId ) -> bool { + let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); + let mut total_staking_balance: u64 = 0; + for hotkey in all_staked_keys { + total_staking_balance += Self::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + } + total_staking_balance += Self::get_coldkey_balance(&coldkey); + total_staking_balance >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + } + /// Schedules a coldkey swap to a new coldkey with arbitration. /// /// # Arguments @@ -238,7 +248,7 @@ impl Pallet { // Check minimum amount of TAO (1 TAO) ensure!( - Self::get_coldkey_balance(old_coldkey) >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + Self::meets_min_allowed_coldkey_balance(&old_coldkey), Error::::InsufficientBalanceToPerformColdkeySwap ); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index caf6c75eb..3ead79d88 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3980,3 +3980,41 @@ fn next_block() { System::on_initialize(System::block_number()); SubtensorModule::on_initialize(System::block_number()); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_coldkey_meets_enough --exact --nocapture +#[test] +fn test_coldkey_meets_enough() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(2); + let netuid = 1u16; + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + let current_block = SubtensorModule::get_current_block_as_u64(); + let (work1, nonce1) = generate_valid_pow(&coldkey, current_block, U256::from(10_000_000u64)); + assert_err!( + SubtensorModule::do_schedule_coldkey_swap( + &coldkey.clone(), + &new_coldkey, + work1.to_fixed_bytes().to_vec(), + current_block, + nonce1 + ), + Error::::InsufficientBalanceToPerformColdkeySwap + ); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &coldkey.clone(), + &new_coldkey, + work1.to_fixed_bytes().to_vec(), + current_block, + nonce1 + )); + + + }); +} diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index a389b5bd9..c306089bf 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1367,20 +1367,12 @@ fn test_coldkey_has_associated_hotkeys() { // Setup initial state add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); - // Check if coldkey has associated hotkeys - assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); - - // Check for a coldkey without associated hotkeys - let unassociated_coldkey = U256::from(3); - assert!(!SubtensorModule::coldkey_has_associated_hotkeys( - &unassociated_coldkey - )); }); } - // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_swap_total --exact --nocapture #[test] fn test_coldkey_swap_total() { From 41feafc908a2133dd54d599d1d8a4b4f9771bb83 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 00:24:27 +0400 Subject: [PATCH 118/185] chore: bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0afaec385..cdd8f7df6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 161, + spec_version: 188, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From b1feb3cbc9d5f276bc8e2d8bec9fd92756735f74 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 01:22:49 +0400 Subject: [PATCH 119/185] feat: reduce arb period to 18 hours for testnet --- pallets/subtensor/src/lib.rs | 6 ++++-- pallets/subtensor/tests/staking.rs | 18 +++++++++--------- runtime/src/lib.rs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d57445824..ccce8cb5a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -394,9 +394,11 @@ pub mod pallet { vec![] } #[pallet::type_value] - /// Default stake interval. + /// Default arbitration period. + /// This value represents the default arbitration period in blocks. + /// The period is set to 18 hours, assuming a block time of 12 seconds. pub fn DefaultArbitrationPeriod() -> u64 { - 7200 * 3 + 5400 // 18 hours * 60 minutes/hour * 5 blocks/minute } #[pallet::storage] // ---- StorageItem Global Used Work. pub type ArbitrationPeriod = diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 3ead79d88..829762417 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3188,7 +3188,7 @@ fn test_arbitrated_coldkey_swap_success() { ); // Check that drain block is set correctly - let drain_block: u64 = 7200 * 3 + 1; + let drain_block: u64 = 5400 + 1; log::info!( "ColdkeysToSwapAtBlock before scheduling: {:?}", @@ -3205,7 +3205,7 @@ fn test_arbitrated_coldkey_swap_success() { pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block) ); - // Make 7200 * 4 blocks pass + // Make 5400 blocks pass run_to_block(drain_block); // Run unstaking @@ -3385,8 +3385,8 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { nonce )); - // Make 7200 * 4 blocks pass, simulating on_idle for each block - let drain_block: u64 = 7200 * 3 + 1; + // Make 5400 blocks pass, simulating on_idle for each block + let drain_block: u64 = 5400 + 1; for _ in 0..drain_block { next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3446,8 +3446,8 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { nonce )); - // Make 7200 * 4 blocks pass, simulating on_idle for each block - let drain_block: u64 = 7200 * 3 + 1; + // Make 5400 blocks pass, simulating on_idle for each block + let drain_block: u64 = 5400 + 1; for _ in 0..drain_block { next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3530,7 +3530,7 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { ); // Simulate the passage of blocks and on_idle calls - for i in 0..(7200 * 3 + 1) { + for i in 0..(5400 + 1) { // Simulate 10 blocks next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3744,8 +3744,8 @@ fn test_concurrent_arbitrated_coldkey_swaps() { current_block, nonce2 )); - // Make 7200 * 3 blocks pass - let drain_block: u64 = 7200 * 3 + 1; + // Make 5400 blocks pass + let drain_block: u64 = 5400 + 1; run_to_block(drain_block); // Run arbitration diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index cdd8f7df6..ea4dded94 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 188, + spec_version: 189, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From e4c9ef2e9518feb0bc03dca7ae2a74489fd9164d Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 03:01:19 +0400 Subject: [PATCH 120/185] feat: allow subnet owners to swap without funds , todo: fix test_comprehensive_coldkey_swap_scenarios --- pallets/subtensor/src/swap.rs | 16 ++- pallets/subtensor/tests/staking.rs | 188 ++++++++++++++++++++++++++++- 2 files changed, 197 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index b65e5cc43..4dd78d2e0 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -246,11 +246,17 @@ impl Pallet { ) -> DispatchResult { ensure!(old_coldkey != new_coldkey, Error::::SameColdkey); - // Check minimum amount of TAO (1 TAO) - ensure!( - Self::meets_min_allowed_coldkey_balance(&old_coldkey), - Error::::InsufficientBalanceToPerformColdkeySwap - ); + // Check if the old_coldkey is a subnet owner for any network + let is_subnet_owner = (0..=TotalNetworks::::get()) + .any(|netuid| SubnetOwner::::get(netuid) == *old_coldkey); + + // Only check the minimum balance if the old_coldkey is not a subnet owner + if !is_subnet_owner { + ensure!( + Self::meets_min_allowed_coldkey_balance(&old_coldkey), + Error::::InsufficientBalanceToPerformColdkeySwap + ); + } // Get current destination coldkeys let mut destination_coldkeys: Vec = diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 829762417..7d52011c7 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3992,7 +3992,8 @@ fn test_coldkey_meets_enough() { add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow(&coldkey, current_block, U256::from(10_000_000u64)); + let (work1, nonce1) = + generate_valid_pow(&coldkey, current_block, U256::from(10_000_000u64)); assert_err!( SubtensorModule::do_schedule_coldkey_swap( &coldkey.clone(), @@ -4014,7 +4015,190 @@ fn test_coldkey_meets_enough() { current_block, nonce1 )); - + }); +} + +#[test] +fn test_comprehensive_coldkey_swap_scenarios() { + new_test_ext(1).execute_with(|| { + // Set arbitration period to 5 blocks + ArbitrationPeriod::::put(5); + + let subnet_owner1 = U256::from(1); + let subnet_owner2 = U256::from(2); + let regular_user = U256::from(3); + let new_coldkey1 = U256::from(4); + let new_coldkey2 = U256::from(5); + let new_coldkey3 = U256::from(6); + let netuid1 = 1; + let netuid2 = 2; + + // Add networks and register subnet owners + add_network(netuid1, 13, 0); + add_network(netuid2, 13, 0); + SubnetOwner::::insert(netuid1, subnet_owner1); + SubnetOwner::::insert(netuid2, subnet_owner2); + + // Add balance to regular user + SubtensorModule::add_balance_to_coldkey_account( + ®ular_user, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP * 2, + ); + let current_block = SubtensorModule::get_current_block_as_u64(); + + // Schedule swaps for subnet owners and regular user + let (work1, nonce1) = + generate_valid_pow(&subnet_owner1, current_block, U256::from(10_000_000u64)); + let (work2, nonce2) = + generate_valid_pow(&subnet_owner2, current_block, U256::from(20_000_000u64)); + let (work3, nonce3) = + generate_valid_pow(®ular_user, current_block, U256::from(30_000_000u64)); + + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &subnet_owner1, + &new_coldkey1, + work1.to_fixed_bytes().to_vec(), + current_block, + nonce1 + )); + + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &subnet_owner2, + &new_coldkey2, + work2.to_fixed_bytes().to_vec(), + current_block, + nonce2 + )); + + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + ®ular_user, + &new_coldkey3, + work3.to_fixed_bytes().to_vec(), + current_block, + nonce3 + )); + + // Check if swaps were scheduled correctly + assert_eq!( + ColdkeySwapDestinations::::get(subnet_owner1), + vec![new_coldkey1] + ); + assert_eq!( + ColdkeySwapDestinations::::get(subnet_owner2), + vec![new_coldkey2] + ); + assert_eq!( + ColdkeySwapDestinations::::get(regular_user), + vec![new_coldkey3] + ); + + // Run through the arbitration period plus one block + for i in 0..6 { + next_block(); + SubtensorModule::on_idle(System::block_number(), Weight::MAX); + + log::info!( + "Block {}: Coldkey in arbitration: {}, Swap destinations: {:?}", + i + 1, + SubtensorModule::coldkey_in_arbitration(&subnet_owner1), + ColdkeySwapDestinations::::get(subnet_owner1) + ); + + // Test edge case: try to schedule another swap during arbitration + if i == 2 { + let (work4, nonce4) = generate_valid_pow( + &subnet_owner1, + current_block + i as u64, + U256::from(40_000_000u64), + ); + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &subnet_owner1, + &new_coldkey2, + work4.to_fixed_bytes().to_vec(), + current_block + i as u64, + nonce4 + )); + // This should add new_coldkey2 to subnet_owner1's destinations + assert_eq!( + ColdkeySwapDestinations::::get(subnet_owner1), + vec![new_coldkey1, new_coldkey2] + ); + } + } + + // Check if swaps have been executed + log::info!( + "After arbitration period - Swap destinations for subnet_owner1: {:?}", + ColdkeySwapDestinations::::get(subnet_owner1) + ); + assert_eq!( + ColdkeySwapDestinations::::get(subnet_owner1), + vec![new_coldkey1, new_coldkey2], + "ColdkeySwapDestinations for subnet_owner1 should still contain two destinations after arbitration period" + ); + assert!(ColdkeySwapDestinations::::get(subnet_owner2).is_empty()); + assert!(ColdkeySwapDestinations::::get(regular_user).is_empty()); + + // Verify that subnet ownerships have NOT been transferred for subnet_owner1 + assert_eq!(SubnetOwner::::get(netuid1), subnet_owner1); + // But subnet_owner2's ownership should have been transferred + assert_eq!(SubnetOwner::::get(netuid2), new_coldkey2); + + // Verify regular user's balance has been transferred + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey3), + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP * 2 + ); + assert_eq!(SubtensorModule::get_coldkey_balance(®ular_user), 0); + + // Test multiple calls for the same subnet owner + let (work5, nonce5) = + generate_valid_pow(&new_coldkey1, current_block + 7, U256::from(50_000_000u64)); + let (work6, nonce6) = + generate_valid_pow(&new_coldkey1, current_block + 7, U256::from(60_000_000u64)); + + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &new_coldkey1, + &subnet_owner1, + work5.to_fixed_bytes().to_vec(), + current_block + 7, + nonce5 + )); + + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &new_coldkey1, + &subnet_owner2, + work6.to_fixed_bytes().to_vec(), + current_block + 7, + nonce6 + )); + + assert_eq!( + ColdkeySwapDestinations::::get(new_coldkey1), + vec![subnet_owner1, subnet_owner2] + ); + + // Run through another arbitration period plus one block + for i in 0..6 { + next_block(); + SubtensorModule::on_idle(System::block_number(), Weight::MAX); + + log::info!( + "Block {}: Coldkey in arbitration: {}, Swap destinations: {:?}", + i + 7, + SubtensorModule::coldkey_in_arbitration(&new_coldkey1), + ColdkeySwapDestinations::::get(new_coldkey1) + ); + } + + // Check final state + log::info!( + "Final state - Swap destinations for new_coldkey1: {:?}", + ColdkeySwapDestinations::::get(new_coldkey1) + ); + assert!(ColdkeySwapDestinations::::get(new_coldkey1).is_empty()); + assert_eq!(SubnetOwner::::get(netuid1), subnet_owner1); + assert_eq!(SubnetOwner::::get(netuid2), subnet_owner2); }); } From af555d727bf205e792e920591be27927eb002206 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 14:35:13 +0400 Subject: [PATCH 121/185] chore: introduce difficulty var to make tests shorter , fix test_comprehensive_coldkey_swap_scenarios --- pallets/subtensor/src/lib.rs | 13 ++ pallets/subtensor/src/swap.rs | 16 +- pallets/subtensor/tests/mock.rs | 2 + pallets/subtensor/tests/staking.rs | 169 +++++++-------- pallets/subtensor/tests/swap.rs | 334 +++++++++++++++++++++-------- runtime/src/lib.rs | 2 + 6 files changed, 356 insertions(+), 180 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ccce8cb5a..8eddc4937 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -87,6 +87,7 @@ pub mod pallet { /// Minimum balance required to perform a coldkey swap pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 1_000_000_000; // 1 TAO in RAO + #[pallet::pallet] #[pallet::without_storage_info] #[pallet::storage_version(STORAGE_VERSION)] @@ -255,6 +256,9 @@ pub mod pallet { /// A flag to indicate if Liquid Alpha is enabled. #[pallet::constant] type LiquidAlphaOn: Get; + /// The base difficulty for proof of work for coldkey swaps + #[pallet::constant] + type InitialBaseDifficulty: Get; } /// Alias for the account ID. @@ -331,6 +335,12 @@ pub mod pallet { 360 } + /// Default base difficulty for proof of work for coldkey swaps + #[pallet::type_value] + pub fn DefaultBaseDifficulty() -> u64 { + T::InitialBaseDifficulty::get() + } + #[pallet::storage] // --- ITEM ( total_stake ) pub type TotalStake = StorageValue<_, u64, ValueQuery>; #[pallet::storage] // --- ITEM ( default_take ) @@ -344,6 +354,9 @@ pub mod pallet { #[pallet::storage] // --- ITEM (target_stakes_per_interval) pub type TargetStakesPerInterval = StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; + + #[pallet::storage] // --- ITEM ( base_difficulty ) + pub type BaseDifficulty = StorageValue<_, u64, ValueQuery, DefaultBaseDifficulty>; #[pallet::storage] // --- ITEM (default_stake_interval) pub type StakeInterval = StorageValue<_, u64, ValueQuery, DefaultStakeInterval>; #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 4dd78d2e0..1a272f216 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -199,7 +199,7 @@ impl Pallet { } } - pub fn meets_min_allowed_coldkey_balance( coldkey: &T::AccountId ) -> bool { + pub fn meets_min_allowed_coldkey_balance(coldkey: &T::AccountId) -> bool { let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); let mut total_staking_balance: u64 = 0; for hotkey in all_staked_keys { @@ -225,12 +225,12 @@ impl Pallet { /// /// # Errors /// - /// This function will return an error if: - /// - The old coldkey is the same as the new coldkey. - /// - The new coldkey is already in the list of destination coldkeys. - /// - There are already 2 destination coldkeys for the old coldkey. - /// - The old coldkey doesn't have the minimum required TAO balance. - /// - The proof of work is invalid or doesn't meet the required difficulty. + + /// - `SameColdkey`: The old coldkey is the same as the new coldkey. + /// - `DuplicateColdkey`: The new coldkey is already in the list of destination coldkeys. + /// - `MaxColdkeyDestinationsReached`: There are already the maximum allowed destination coldkeys for the old coldkey. + /// - `InsufficientBalanceToPerformColdkeySwap`: The old coldkey doesn't have the minimum required TAO balance. + /// - `InvalidDifficulty`: The proof of work is invalid or doesn't meet the required difficulty. /// /// # Notes /// @@ -319,7 +319,7 @@ impl Pallet { /// Calculate the proof of work difficulty based on the number of swap attempts #[allow(clippy::arithmetic_side_effects)] pub fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { - let base_difficulty: U256 = U256::from(10_000_000); // Base difficulty + let base_difficulty: U256 = U256::from(BaseDifficulty::::get()); // Base difficulty base_difficulty.saturating_mul(U256::from(2).pow(U256::from(swap_attempts))) } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 9e21f2a63..c3a864075 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -168,6 +168,7 @@ parameter_types! { pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn + pub const SubtensorInitialBaseDifficulty: u64 = 10_000; // Base difficulty } // Configure collective pallet for council @@ -372,6 +373,7 @@ impl pallet_subtensor::Config for Test { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; + type InitialBaseDifficulty = SubtensorInitialBaseDifficulty; } impl pallet_utility::Config for Test { diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 7d52011c7..9d3a4698b 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3167,8 +3167,11 @@ fn test_arbitrated_coldkey_swap_success() { let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work, nonce) = generate_valid_pow( + ¤t_coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); SubtensorModule::add_balance_to_coldkey_account( ¤t_coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, @@ -3239,8 +3242,11 @@ fn test_arbitrated_coldkey_swap_same_coldkey() { let (current_coldkey, _hotkey, _) = setup_test_environment(); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work, nonce) = generate_valid_pow( + ¤t_coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); assert_noop!( SubtensorModule::do_schedule_coldkey_swap( @@ -3290,8 +3296,11 @@ fn test_arbitrated_coldkey_swap_no_balance() { // Generate valid PoW let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work, nonce) = generate_valid_pow( + ¤t_coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); // Try to schedule coldkey swap let result = SubtensorModule::do_schedule_coldkey_swap( @@ -3373,8 +3382,11 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { assert_eq!(Balances::total_balance(&new_coldkey), 0); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work, nonce) = generate_valid_pow( + ¤t_coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); // Schedule coldkey swap assert_ok!(SubtensorModule::do_schedule_coldkey_swap( @@ -3435,8 +3447,11 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { )); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work, nonce) = generate_valid_pow( + ¤t_coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( ¤t_coldkey.clone(), @@ -3476,6 +3491,9 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { #[test] fn test_arbitrated_coldkey_swap_multiple_arbitrations() { new_test_ext(1).execute_with(|| { + // Set a very low base difficulty for testing + BaseDifficulty::::put(1); + // Create coldkey with three choices. let coldkey: AccountId = U256::from(1); let new_coldkey1: AccountId = U256::from(2); @@ -3493,12 +3511,11 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { register_ok_neuron(1, hotkey, coldkey, 0); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = - generate_valid_pow(&coldkey, current_block, U256::from(10_000_000u64)); - let (work2, nonce2) = - generate_valid_pow(&coldkey, current_block, U256::from(20_000_000u64)); - let (work3, nonce3) = - generate_valid_pow(&coldkey, current_block, U256::from(30_000_000u64)); + + // Generate valid PoW for each swap attempt + let (work1, nonce1) = generate_valid_pow(&coldkey, current_block, U256::from(1)); + let (work2, nonce2) = generate_valid_pow(&coldkey, current_block, U256::from(2)); + let (work3, nonce3) = generate_valid_pow(&coldkey, current_block, U256::from(4)); // Schedule three swaps assert_ok!(SubtensorModule::do_schedule_coldkey_swap( @@ -3531,7 +3548,6 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { // Simulate the passage of blocks and on_idle calls for i in 0..(5400 + 1) { - // Simulate 10 blocks next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3655,8 +3671,11 @@ fn test_arbitration_period_extension() { let another_coldkey = U256::from(4); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(10_000_000u64)); + let (work1, nonce1) = generate_valid_pow( + ¤t_coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, U256::from(20_000_000u64)); SubtensorModule::add_balance_to_coldkey_account( @@ -3725,10 +3744,16 @@ fn test_concurrent_arbitrated_coldkey_swaps() { ); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = - generate_valid_pow(&coldkey1, current_block, U256::from(10_000_000u64)); - let (work2, nonce2) = - generate_valid_pow(&coldkey2, current_block, U256::from(10_000_000u64)); + let (work1, nonce1) = generate_valid_pow( + &coldkey1, + current_block, + U256::from(BaseDifficulty::::get()), + ); + let (work2, nonce2) = generate_valid_pow( + &coldkey2, + current_block, + U256::from(BaseDifficulty::::get()), + ); // Schedule swaps for both coldkeys assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &coldkey1.clone(), @@ -3773,7 +3798,7 @@ fn test_get_remaining_arbitration_period() { let (work, nonce) = generate_valid_pow( &coldkey_account_id, current_block, - U256::from(10_000_000u64), + U256::from(BaseDifficulty::::get()), ); SubtensorModule::add_balance_to_coldkey_account( @@ -3833,7 +3858,7 @@ fn test_transfer_coldkey_in_arbitration() { let (work, nonce) = generate_valid_pow( &coldkey_account_id, current_block, - U256::from(10_000_000u64), + U256::from(BaseDifficulty::::get()), ); // Schedule a coldkey swap to put the coldkey in arbitration @@ -3876,8 +3901,11 @@ fn test_add_stake_coldkey_in_arbitration() { ); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(&coldkey_account_id, current_block, U256::from(1_000_000u64)); + let (work, nonce) = generate_valid_pow( + &coldkey_account_id, + current_block, + U256::from(BaseDifficulty::::get()), + ); // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( @@ -3916,8 +3944,11 @@ fn test_remove_stake_coldkey_in_arbitration() { SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = - generate_valid_pow(&coldkey_account_id, current_block, U256::from(1_000_000u64)); + let (work, nonce) = generate_valid_pow( + &coldkey_account_id, + current_block, + U256::from(BaseDifficulty::::get()), + ); // Schedule a coldkey swap to put the coldkey in arbitration assert_ok!(SubtensorModule::do_schedule_coldkey_swap( @@ -3992,8 +4023,11 @@ fn test_coldkey_meets_enough() { add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = - generate_valid_pow(&coldkey, current_block, U256::from(10_000_000u64)); + let (work1, nonce1) = generate_valid_pow( + &coldkey, + current_block, + U256::from(BaseDifficulty::::get()), + ); assert_err!( SubtensorModule::do_schedule_coldkey_swap( &coldkey.clone(), @@ -4039,21 +4073,29 @@ fn test_comprehensive_coldkey_swap_scenarios() { SubnetOwner::::insert(netuid1, subnet_owner1); SubnetOwner::::insert(netuid2, subnet_owner2); - // Add balance to regular user + // Add balance to subnet owners and regular user + SubtensorModule::add_balance_to_coldkey_account( + &subnet_owner1, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); + SubtensorModule::add_balance_to_coldkey_account( + &subnet_owner2, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + ); SubtensorModule::add_balance_to_coldkey_account( ®ular_user, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP * 2, ); + // Set a very low base difficulty for testing + BaseDifficulty::::put(1); + let current_block = SubtensorModule::get_current_block_as_u64(); // Schedule swaps for subnet owners and regular user - let (work1, nonce1) = - generate_valid_pow(&subnet_owner1, current_block, U256::from(10_000_000u64)); - let (work2, nonce2) = - generate_valid_pow(&subnet_owner2, current_block, U256::from(20_000_000u64)); - let (work3, nonce3) = - generate_valid_pow(®ular_user, current_block, U256::from(30_000_000u64)); + let (work1, nonce1) = generate_valid_pow(&subnet_owner1, current_block, U256::from(BaseDifficulty::::get())); + let (work2, nonce2) = generate_valid_pow(&subnet_owner2, current_block, U256::from(2) * U256::from(BaseDifficulty::::get())); + let (work3, nonce3) = generate_valid_pow(®ular_user, current_block, U256::from(3)* U256::from(BaseDifficulty::::get()),); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &subnet_owner1, @@ -4110,7 +4152,7 @@ fn test_comprehensive_coldkey_swap_scenarios() { let (work4, nonce4) = generate_valid_pow( &subnet_owner1, current_block + i as u64, - U256::from(40_000_000u64), + U256::from(4) * U256::from(BaseDifficulty::::get()), ); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &subnet_owner1, @@ -4151,54 +4193,5 @@ fn test_comprehensive_coldkey_swap_scenarios() { MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP * 2 ); assert_eq!(SubtensorModule::get_coldkey_balance(®ular_user), 0); - - // Test multiple calls for the same subnet owner - let (work5, nonce5) = - generate_valid_pow(&new_coldkey1, current_block + 7, U256::from(50_000_000u64)); - let (work6, nonce6) = - generate_valid_pow(&new_coldkey1, current_block + 7, U256::from(60_000_000u64)); - - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &new_coldkey1, - &subnet_owner1, - work5.to_fixed_bytes().to_vec(), - current_block + 7, - nonce5 - )); - - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &new_coldkey1, - &subnet_owner2, - work6.to_fixed_bytes().to_vec(), - current_block + 7, - nonce6 - )); - - assert_eq!( - ColdkeySwapDestinations::::get(new_coldkey1), - vec![subnet_owner1, subnet_owner2] - ); - - // Run through another arbitration period plus one block - for i in 0..6 { - next_block(); - SubtensorModule::on_idle(System::block_number(), Weight::MAX); - - log::info!( - "Block {}: Coldkey in arbitration: {}, Swap destinations: {:?}", - i + 7, - SubtensorModule::coldkey_in_arbitration(&new_coldkey1), - ColdkeySwapDestinations::::get(new_coldkey1) - ); - } - - // Check final state - log::info!( - "Final state - Swap destinations for new_coldkey1: {:?}", - ColdkeySwapDestinations::::get(new_coldkey1) - ); - assert!(ColdkeySwapDestinations::::get(new_coldkey1).is_empty()); - assert_eq!(SubnetOwner::::get(netuid1), subnet_owner1); - assert_eq!(SubnetOwner::::get(netuid2), subnet_owner2); }); } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index c306089bf..90ebdfdcb 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1368,11 +1368,9 @@ fn test_coldkey_has_associated_hotkeys() { add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); - }); } - // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_swap_total --exact --nocapture #[test] fn test_coldkey_swap_total() { @@ -1408,95 +1406,263 @@ fn test_coldkey_swap_total() { register_ok_neuron(netuid1, delegate1, delegate1, 0); register_ok_neuron(netuid2, delegate2, delegate2, 0); register_ok_neuron(netuid3, delegate3, delegate3, 0); - assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(coldkey), hotkey1, u16::MAX / 10)); - assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(coldkey), hotkey2, u16::MAX / 10)); - assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(coldkey), hotkey3, u16::MAX / 10)); - assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(delegate1), delegate1, u16::MAX / 10)); - assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(delegate2), delegate2, u16::MAX / 10)); - assert_ok!(SubtensorModule::do_become_delegate(<::RuntimeOrigin>::signed(delegate3), delegate3, u16::MAX / 10)); - - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), hotkey1, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), hotkey2, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), hotkey3, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), delegate1, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), delegate2, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(coldkey), delegate3, 100 )); - - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate1), hotkey1, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate2), hotkey2, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate3), hotkey3, 100 )); - - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate1), delegate1, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate2), delegate2, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(delegate3), delegate3, 100 )); - - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator1), hotkey1, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator2), hotkey2, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator3), hotkey3, 100 )); - - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator1), delegate1, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator2), delegate2, 100 )); - assert_ok!(SubtensorModule::add_stake(<::RuntimeOrigin>::signed(nominator3), delegate3, 100 )); - - assert_eq!( SubtensorModule::get_owned_hotkeys(&coldkey), vec![hotkey1, hotkey2, hotkey3] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&coldkey), vec![hotkey1, hotkey2, hotkey3, delegate1, delegate2, delegate3] ); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate1), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate2), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate3), 300 ); - - - assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate1), vec![delegate1] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate2), vec![delegate2] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate3), vec![delegate3] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate1), vec![delegate1, hotkey1] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate2), vec![delegate2, hotkey2] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate3), vec![delegate3, hotkey3] ); - - assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator1), vec![] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator2), vec![] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator3), vec![] ); - - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator1), vec![hotkey1, delegate1] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator2), vec![hotkey2, delegate2] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator3), vec![hotkey3, delegate3] ); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey), + hotkey1, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey), + hotkey2, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(coldkey), + hotkey3, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(delegate1), + delegate1, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(delegate2), + delegate2, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(delegate3), + delegate3, + u16::MAX / 10 + )); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + hotkey1, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + hotkey2, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + hotkey3, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + delegate1, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + delegate2, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + delegate3, + 100 + )); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(delegate1), + hotkey1, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(delegate2), + hotkey2, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(delegate3), + hotkey3, + 100 + )); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(delegate1), + delegate1, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(delegate2), + delegate2, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(delegate3), + delegate3, + 100 + )); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(nominator1), + hotkey1, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(nominator2), + hotkey2, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(nominator3), + hotkey3, + 100 + )); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(nominator1), + delegate1, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(nominator2), + delegate2, + 100 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(nominator3), + delegate3, + 100 + )); + + assert_eq!( + SubtensorModule::get_owned_hotkeys(&coldkey), + vec![hotkey1, hotkey2, hotkey3] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&coldkey), + vec![hotkey1, hotkey2, hotkey3, delegate1, delegate2, delegate3] + ); + assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate1), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate2), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate3), 300); + + assert_eq!( + SubtensorModule::get_owned_hotkeys(&delegate1), + vec![delegate1] + ); + assert_eq!( + SubtensorModule::get_owned_hotkeys(&delegate2), + vec![delegate2] + ); + assert_eq!( + SubtensorModule::get_owned_hotkeys(&delegate3), + vec![delegate3] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&delegate1), + vec![delegate1, hotkey1] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&delegate2), + vec![delegate2, hotkey2] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&delegate3), + vec![delegate3, hotkey3] + ); + + assert_eq!(SubtensorModule::get_owned_hotkeys(&nominator1), vec![]); + assert_eq!(SubtensorModule::get_owned_hotkeys(&nominator2), vec![]); + assert_eq!(SubtensorModule::get_owned_hotkeys(&nominator3), vec![]); + + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&nominator1), + vec![hotkey1, delegate1] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&nominator2), + vec![hotkey2, delegate2] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&nominator3), + vec![hotkey3, delegate3] + ); // Perform the swap let new_coldkey = U256::from(1100); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600 ); - assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, &new_coldkey )); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), 600 ); + assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey + )); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + 600 + ); // Check everything is swapped. - assert_eq!( SubtensorModule::get_owned_hotkeys(&new_coldkey), vec![hotkey1, hotkey2, hotkey3] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&new_coldkey), vec![hotkey1, hotkey2, hotkey3, delegate1, delegate2, delegate3] ); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), 600 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate1), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate2), 300 ); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate3), 300 ); - - assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate1), vec![delegate1] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate2), vec![delegate2] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&delegate3), vec![delegate3] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate1), vec![delegate1, hotkey1] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate2), vec![delegate2, hotkey2] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&delegate3), vec![delegate3, hotkey3] ); - - assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator1), vec![] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator2), vec![] ); - assert_eq!( SubtensorModule::get_owned_hotkeys(&nominator3), vec![] ); - - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator1), vec![hotkey1, delegate1] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator2), vec![hotkey2, delegate2] ); - assert_eq!( SubtensorModule::get_all_staked_hotkeys(&nominator3), vec![hotkey3, delegate3] ); + assert_eq!( + SubtensorModule::get_owned_hotkeys(&new_coldkey), + vec![hotkey1, hotkey2, hotkey3] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&new_coldkey), + vec![hotkey1, hotkey2, hotkey3, delegate1, delegate2, delegate3] + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + 600 + ); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate1), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate2), 300); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate3), 300); + + assert_eq!( + SubtensorModule::get_owned_hotkeys(&delegate1), + vec![delegate1] + ); + assert_eq!( + SubtensorModule::get_owned_hotkeys(&delegate2), + vec![delegate2] + ); + assert_eq!( + SubtensorModule::get_owned_hotkeys(&delegate3), + vec![delegate3] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&delegate1), + vec![delegate1, hotkey1] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&delegate2), + vec![delegate2, hotkey2] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&delegate3), + vec![delegate3, hotkey3] + ); + assert_eq!(SubtensorModule::get_owned_hotkeys(&nominator1), vec![]); + assert_eq!(SubtensorModule::get_owned_hotkeys(&nominator2), vec![]); + assert_eq!(SubtensorModule::get_owned_hotkeys(&nominator3), vec![]); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&nominator1), + vec![hotkey1, delegate1] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&nominator2), + vec![hotkey2, delegate2] + ); + assert_eq!( + SubtensorModule::get_all_staked_hotkeys(&nominator3), + vec![hotkey3, delegate3] + ); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ea4dded94..9be039e6b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -879,6 +879,7 @@ parameter_types! { pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn + pub const SubtensorInitialBaseDifficulty: u64 = 10_000_000; // Base difficulty } impl pallet_subtensor::Config for Runtime { @@ -934,6 +935,7 @@ impl pallet_subtensor::Config for Runtime { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; + type InitialBaseDifficulty = SubtensorInitialBaseDifficulty; } use sp_runtime::BoundedVec; From a14337b07bc03c1281dcaa882b993c034f9c09d4 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 9 Jul 2024 19:00:15 +0800 Subject: [PATCH 122/185] make it easier --- pallets/subtensor/tests/staking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 9d3a4698b..17017ce97 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -4094,8 +4094,8 @@ fn test_comprehensive_coldkey_swap_scenarios() { // Schedule swaps for subnet owners and regular user let (work1, nonce1) = generate_valid_pow(&subnet_owner1, current_block, U256::from(BaseDifficulty::::get())); - let (work2, nonce2) = generate_valid_pow(&subnet_owner2, current_block, U256::from(2) * U256::from(BaseDifficulty::::get())); - let (work3, nonce3) = generate_valid_pow(®ular_user, current_block, U256::from(3)* U256::from(BaseDifficulty::::get()),); + let (work2, nonce2) = generate_valid_pow(&subnet_owner2, current_block, U256::from(BaseDifficulty::::get())); + let (work3, nonce3) = generate_valid_pow(®ular_user, current_block, U256::from(BaseDifficulty::::get())); assert_ok!(SubtensorModule::do_schedule_coldkey_swap( &subnet_owner1, From 83819ffe4bf70f71e8db6533d693f1db4b80b750 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 9 Jul 2024 19:02:33 +0800 Subject: [PATCH 123/185] fix fmt --- pallets/subtensor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8eddc4937..787989f17 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -87,7 +87,6 @@ pub mod pallet { /// Minimum balance required to perform a coldkey swap pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 1_000_000_000; // 1 TAO in RAO - #[pallet::pallet] #[pallet::without_storage_info] #[pallet::storage_version(STORAGE_VERSION)] From 4563e782d23d751e13768c1fa5a9fa5f96b6ee8a Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 15:15:57 +0400 Subject: [PATCH 124/185] feat: get total delegated stake --- pallets/subtensor/src/delegate_info.rs | 66 +++++++-- pallets/subtensor/tests/staking.rs | 196 +++++++++++++++++++++++++ 2 files changed, 246 insertions(+), 16 deletions(-) diff --git a/pallets/subtensor/src/delegate_info.rs b/pallets/subtensor/src/delegate_info.rs index 1f8b06b69..38f97bb8a 100644 --- a/pallets/subtensor/src/delegate_info.rs +++ b/pallets/subtensor/src/delegate_info.rs @@ -40,11 +40,7 @@ impl Pallet { let mut emissions_per_day: U64F64 = U64F64::from_num(0); for netuid in registrations.iter() { - let _uid = Self::get_uid_for_net_and_hotkey(*netuid, &delegate.clone()); - if _uid.is_err() { - continue; // this should never happen - } else { - let uid = _uid.expect("Delegate's UID should be ok"); + if let Ok(uid) = Self::get_uid_for_net_and_hotkey(*netuid, &delegate.clone()) { let validator_permit = Self::get_validator_permit_for_uid(*netuid, uid); if validator_permit { validator_permits.push((*netuid).into()); @@ -52,9 +48,11 @@ impl Pallet { let emission: U64F64 = Self::get_emission_for_uid(*netuid, uid).into(); let tempo: U64F64 = Self::get_tempo(*netuid).into(); - let epochs_per_day: U64F64 = U64F64::from_num(7200).saturating_div(tempo); - emissions_per_day = - emissions_per_day.saturating_add(emission.saturating_mul(epochs_per_day)); + if tempo > U64F64::from_num(0) { + let epochs_per_day: U64F64 = U64F64::from_num(7200).saturating_div(tempo); + emissions_per_day = + emissions_per_day.saturating_add(emission.saturating_mul(epochs_per_day)); + } } } @@ -63,15 +61,15 @@ impl Pallet { let total_stake: U64F64 = Self::get_total_stake_for_hotkey(&delegate.clone()).into(); - let mut return_per_1000: U64F64 = U64F64::from_num(0); - - if total_stake > U64F64::from_num(0) { - return_per_1000 = emissions_per_day + let return_per_1000: U64F64 = if total_stake > U64F64::from_num(0) { + emissions_per_day .saturating_mul(U64F64::from_num(0.82)) - .saturating_div(total_stake.saturating_div(U64F64::from_num(1000))); - } + .saturating_div(total_stake.saturating_div(U64F64::from_num(1000))) + } else { + U64F64::from_num(0) + }; - return DelegateInfo { + DelegateInfo { delegate_ss58: delegate.clone(), take, nominators, @@ -80,7 +78,7 @@ impl Pallet { validator_permits, return_per_1000: U64F64::to_num::(return_per_1000).into(), total_daily_return: U64F64::to_num::(emissions_per_day).into(), - }; + } } pub fn get_delegate(delegate_account_vec: Vec) -> Option> { @@ -132,4 +130,40 @@ impl Pallet { delegates } + + /// Returns the total delegated stake for a given delegate, excluding the stake from the delegate's owner. + /// + /// # Arguments + /// + /// * `delegate` - A reference to the account ID of the delegate. + /// + /// # Returns + /// + /// * `u64` - The total amount of stake delegated to the delegate, excluding the owner's stake. + /// + /// + /// # Notes + /// + /// This function retrieves the delegate's information and calculates the total stake from all nominators, + /// excluding the stake from the delegate's owner. + pub fn get_total_delegated_stake(delegate: &T::AccountId) -> u64 { + if !>::contains_key(delegate) { + return 0; + } + + // Retrieve the delegate's information + let delegate_info: DelegateInfo = + Self::get_delegate_by_existing_account(delegate.clone()); + + // Retrieve the owner's account ID for the given delegate + let owner: T::AccountId = Self::get_owning_coldkey_for_hotkey(delegate); + + // Calculate the total stake from all nominators, excluding the owner's stake + delegate_info + .nominators + .iter() + .filter(|(nominator, _)| nominator != &owner) // Exclude the owner's stake + .map(|(_, stake)| stake.0 as u64) // Map the stake to u64 + .sum() // Sum the stakes + } } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 17017ce97..39ed875a1 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -13,6 +13,7 @@ use frame_support::sp_runtime::DispatchError; use mock::*; use pallet_balances::Call as BalancesCall; use pallet_subtensor::*; +use serde::de; use sp_core::{H256, U256}; use sp_runtime::traits::SignedExtension; @@ -4195,3 +4196,198 @@ fn test_comprehensive_coldkey_swap_scenarios() { assert_eq!(SubtensorModule::get_coldkey_balance(®ular_user), 0); }); } + +#[test] +fn test_get_total_delegated_stake_after_unstaking() { + new_test_ext(1).execute_with(|| { + let delegate = U256::from(1); + let coldkey = U256::from(2); + let delegator = U256::from(3); + let initial_stake = 2000; + let unstake_amount = 500; + let netuid = 1u16; + let existential_deposit = 1; // Account for the existential deposit + + add_network(netuid, 0, 0); + + register_ok_neuron(netuid, delegate, coldkey, 0); + + // Make the delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(coldkey), + delegate + )); + + // Add balance to delegator + SubtensorModule::add_balance_to_coldkey_account(&delegator, initial_stake); + + // Delegate stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate, + initial_stake + )); + + // Unstake part of the delegation + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(delegator), + delegate, + unstake_amount + )); + + // Calculate the expected delegated stake + let expected_delegated_stake = initial_stake - unstake_amount - existential_deposit; + + // Check the total delegated stake after unstaking + assert_eq!( + SubtensorModule::get_total_delegated_stake(&delegate), + expected_delegated_stake + ); + }); +} + +#[test] +fn test_get_total_delegated_stake_no_delegations() { + new_test_ext(1).execute_with(|| { + let delegate = U256::from(1); + let coldkey = U256::from(2); + let netuid = 1u16; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate, coldkey, 0); + + // Make the delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(coldkey), + delegate + )); + + // Check that there's no delegated stake + assert_eq!(SubtensorModule::get_total_delegated_stake(&delegate), 0); + }); +} + +#[test] +fn test_get_total_delegated_stake_single_delegator() { + new_test_ext(1).execute_with(|| { + let delegate = U256::from(1); + let coldkey = U256::from(2); + let delegator = U256::from(3); + let stake_amount = 1000; + let netuid = 1u16; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate, coldkey, 0); + + // Make the delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(coldkey), + delegate + )); + + // Add balance to delegator + SubtensorModule::add_balance_to_coldkey_account(&delegator, stake_amount); + + // Delegate stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate, + stake_amount + )); + + // Check the total delegated stake + assert_eq!( + SubtensorModule::get_total_delegated_stake(&delegate), + stake_amount - 1 // Subtract 1 for existential deposit + ); + }); +} + +#[test] +fn test_get_total_delegated_stake_multiple_delegators() { + new_test_ext(1).execute_with(|| { + let delegate = U256::from(1); + let coldkey = U256::from(2); + let delegator1 = U256::from(3); + let delegator2 = U256::from(4); + let stake_amount1 = 1000; + let stake_amount2 = 2000; + let netuid = 1u16; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate, coldkey, 0); + + // Make the delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(coldkey), + delegate + )); + + // Add balance to delegators + SubtensorModule::add_balance_to_coldkey_account(&delegator1, stake_amount1); + SubtensorModule::add_balance_to_coldkey_account(&delegator2, stake_amount2); + + // Delegate stakes + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator1), + delegate, + stake_amount1 + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator2), + delegate, + stake_amount2 + )); + + // Check the total delegated stake + assert_eq!( + SubtensorModule::get_total_delegated_stake(&delegate), + stake_amount1 + stake_amount2 - 2 // Subtract 2 for existential deposits + ); + }); +} + +#[test] +fn test_get_total_delegated_stake_exclude_owner_stake() { + new_test_ext(1).execute_with(|| { + let delegate = U256::from(1); + let coldkey = U256::from(2); + let delegator = U256::from(3); + let owner_stake = 5000; + let delegator_stake = 1000; + let netuid = 1u16; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate, coldkey, 0); + + // Make the delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(coldkey), + delegate + )); + + // Add balance to owner and delegator + SubtensorModule::add_balance_to_coldkey_account(&coldkey, owner_stake); + SubtensorModule::add_balance_to_coldkey_account(&delegator, delegator_stake); + + // Owner adds stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + delegate, + owner_stake + )); + + // Delegator adds stake + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate, + delegator_stake + )); + + // Check the total delegated stake (should exclude owner's stake) + assert_eq!( + SubtensorModule::get_total_delegated_stake(&delegate), + delegator_stake - 1 // Subtract 1 for existential deposit + ); + }); +} From 4eefcf9f093cb8584e86373f22de1e11242164d4 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 16:21:17 +0400 Subject: [PATCH 125/185] fix: delegated stake tests, whitelist serve_axon, lints --- pallets/admin-utils/tests/mock.rs | 2 + pallets/subtensor/src/delegate_info.rs | 67 ++-- pallets/subtensor/src/swap.rs | 13 +- pallets/subtensor/tests/staking.rs | 465 +++++++++++++++++++++---- runtime/src/lib.rs | 1 + 5 files changed, 445 insertions(+), 103 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index a78eb6d3d..5f71626ad 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -114,6 +114,7 @@ parameter_types! { pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn + pub const InitialBaseDifficulty: u64 = 10_000; // Base difficulty } impl pallet_subtensor::Config for Test { @@ -169,6 +170,7 @@ impl pallet_subtensor::Config for Test { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; + type InitialBaseDifficulty = InitialBaseDifficulty; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/subtensor/src/delegate_info.rs b/pallets/subtensor/src/delegate_info.rs index 38f97bb8a..7ae50355a 100644 --- a/pallets/subtensor/src/delegate_info.rs +++ b/pallets/subtensor/src/delegate_info.rs @@ -131,39 +131,46 @@ impl Pallet { delegates } - /// Returns the total delegated stake for a given delegate, excluding the stake from the delegate's owner. - /// - /// # Arguments - /// - /// * `delegate` - A reference to the account ID of the delegate. - /// - /// # Returns - /// - /// * `u64` - The total amount of stake delegated to the delegate, excluding the owner's stake. - /// - /// - /// # Notes - /// - /// This function retrieves the delegate's information and calculates the total stake from all nominators, - /// excluding the stake from the delegate's owner. - pub fn get_total_delegated_stake(delegate: &T::AccountId) -> u64 { - if !>::contains_key(delegate) { - return 0; + pub fn get_total_delegated_stake(coldkey: &T::AccountId) -> u64 { + let mut total_delegated = 0u64; + + // Get all hotkeys associated with this coldkey + let hotkeys = StakingHotkeys::::get(coldkey); + + for hotkey in hotkeys { + let owner = Owner::::get(&hotkey); + + for (delegator, stake) in Stake::::iter_prefix(&hotkey) { + if delegator != owner { + total_delegated += stake; + } + } } - // Retrieve the delegate's information - let delegate_info: DelegateInfo = - Self::get_delegate_by_existing_account(delegate.clone()); + log::info!( + "Total delegated stake for coldkey {:?}: {}", + coldkey, + total_delegated + ); + total_delegated + } + + // Helper function to get total delegated stake for a hotkey + pub fn get_total_hotkey_delegated_stake(hotkey: &T::AccountId) -> u64 { + let mut total_delegated = 0u64; - // Retrieve the owner's account ID for the given delegate - let owner: T::AccountId = Self::get_owning_coldkey_for_hotkey(delegate); + // Iterate through all delegators for this hotkey + for (delegator, stake) in Stake::::iter_prefix(hotkey) { + if delegator != Self::get_coldkey_for_hotkey(hotkey) { + total_delegated += stake; + } + } + + total_delegated + } - // Calculate the total stake from all nominators, excluding the owner's stake - delegate_info - .nominators - .iter() - .filter(|(nominator, _)| nominator != &owner) // Exclude the owner's stake - .map(|(_, stake)| stake.0 as u64) // Map the stake to u64 - .sum() // Sum the stakes + // Helper function to get the coldkey associated with a hotkey + pub fn get_coldkey_for_hotkey(hotkey: &T::AccountId) -> T::AccountId { + Owner::::get(hotkey) } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 1a272f216..7a548f289 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -203,9 +203,9 @@ impl Pallet { let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); let mut total_staking_balance: u64 = 0; for hotkey in all_staked_keys { - total_staking_balance += Self::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + total_staking_balance += Self::get_stake_for_coldkey_and_hotkey(coldkey, &hotkey); } - total_staking_balance += Self::get_coldkey_balance(&coldkey); + total_staking_balance += Self::get_coldkey_balance(coldkey); total_staking_balance >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP } @@ -250,10 +250,15 @@ impl Pallet { let is_subnet_owner = (0..=TotalNetworks::::get()) .any(|netuid| SubnetOwner::::get(netuid) == *old_coldkey); + // Check if the old_coldkey has more than 500 TAO delegated + let total_delegated = Self::get_total_delegated_stake(old_coldkey); + let has_sufficient_delegation = total_delegated > 500_000_000_000; // 500 TAO in RAO + // Only check the minimum balance if the old_coldkey is not a subnet owner - if !is_subnet_owner { + // and doesn't have sufficient delegation + if !(is_subnet_owner || has_sufficient_delegation) { ensure!( - Self::meets_min_allowed_coldkey_balance(&old_coldkey), + Self::meets_min_allowed_coldkey_balance(old_coldkey), Error::::InsufficientBalanceToPerformColdkeySwap ); } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 39ed875a1..873c81309 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -13,7 +13,6 @@ use frame_support::sp_runtime::DispatchError; use mock::*; use pallet_balances::Call as BalancesCall; use pallet_subtensor::*; -use serde::de; use sp_core::{H256, U256}; use sp_runtime::traits::SignedExtension; @@ -4200,22 +4199,21 @@ fn test_comprehensive_coldkey_swap_scenarios() { #[test] fn test_get_total_delegated_stake_after_unstaking() { new_test_ext(1).execute_with(|| { - let delegate = U256::from(1); - let coldkey = U256::from(2); + let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); let delegator = U256::from(3); let initial_stake = 2000; let unstake_amount = 500; - let netuid = 1u16; let existential_deposit = 1; // Account for the existential deposit add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); - register_ok_neuron(netuid, delegate, coldkey, 0); - - // Make the delegate a delegate + // Make the account a delegate assert_ok!(SubtensorModule::become_delegate( - RuntimeOrigin::signed(coldkey), - delegate + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey )); // Add balance to delegator @@ -4224,24 +4222,42 @@ fn test_get_total_delegated_stake_after_unstaking() { // Delegate stake assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(delegator), - delegate, + delegate_hotkey, initial_stake )); + // Check initial delegated stake + assert_eq!( + SubtensorModule::get_total_delegated_stake(&delegate_coldkey), + initial_stake - existential_deposit, + "Initial delegated stake is incorrect" + ); + // Unstake part of the delegation assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(delegator), - delegate, + delegate_hotkey, unstake_amount )); // Calculate the expected delegated stake let expected_delegated_stake = initial_stake - unstake_amount - existential_deposit; + // Debug prints + println!("Initial stake: {}", initial_stake); + println!("Unstake amount: {}", unstake_amount); + println!("Existential deposit: {}", existential_deposit); + println!("Expected delegated stake: {}", expected_delegated_stake); + println!( + "Actual delegated stake: {}", + SubtensorModule::get_total_delegated_stake(&delegate_coldkey) + ); + // Check the total delegated stake after unstaking assert_eq!( - SubtensorModule::get_total_delegated_stake(&delegate), - expected_delegated_stake + SubtensorModule::get_total_delegated_stake(&delegate_coldkey), + expected_delegated_stake, + "Delegated stake mismatch after unstaking" ); }); } @@ -4270,35 +4286,49 @@ fn test_get_total_delegated_stake_no_delegations() { #[test] fn test_get_total_delegated_stake_single_delegator() { new_test_ext(1).execute_with(|| { - let delegate = U256::from(1); - let coldkey = U256::from(2); - let delegator = U256::from(3); - let stake_amount = 1000; let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); + let delegator = U256::from(3); + let stake_amount = 999; + let existential_deposit = 1; // Account for the existential deposit add_network(netuid, 0, 0); - register_ok_neuron(netuid, delegate, coldkey, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); - // Make the delegate a delegate + // Make the account a delegate assert_ok!(SubtensorModule::become_delegate( - RuntimeOrigin::signed(coldkey), - delegate + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey )); - // Add balance to delegator + // Add stake from delegator SubtensorModule::add_balance_to_coldkey_account(&delegator, stake_amount); - - // Delegate stake assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(delegator), - delegate, + delegate_hotkey, stake_amount )); - // Check the total delegated stake + // Debug prints + println!("Delegate coldkey: {:?}", delegate_coldkey); + println!("Delegate hotkey: {:?}", delegate_hotkey); + println!("Delegator: {:?}", delegator); + println!("Stake amount: {}", stake_amount); + println!("Existential deposit: {}", existential_deposit); + println!("Total stake for hotkey: {}", SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey)); + println!("Delegated stake for coldkey: {}", SubtensorModule::get_total_delegated_stake(&delegate_coldkey)); + + // Calculate expected delegated stake + let expected_delegated_stake = stake_amount - existential_deposit; + let actual_delegated_stake = SubtensorModule::get_total_delegated_stake(&delegate_coldkey); + assert_eq!( - SubtensorModule::get_total_delegated_stake(&delegate), - stake_amount - 1 // Subtract 1 for existential deposit + actual_delegated_stake, + expected_delegated_stake, + "Total delegated stake should match the delegator's stake minus existential deposit. Expected: {}, Actual: {}", + expected_delegated_stake, + actual_delegated_stake ); }); } @@ -4306,43 +4336,57 @@ fn test_get_total_delegated_stake_single_delegator() { #[test] fn test_get_total_delegated_stake_multiple_delegators() { new_test_ext(1).execute_with(|| { - let delegate = U256::from(1); - let coldkey = U256::from(2); + let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); let delegator1 = U256::from(3); let delegator2 = U256::from(4); - let stake_amount1 = 1000; - let stake_amount2 = 2000; - let netuid = 1u16; + let stake1 = 1000; + let stake2 = 1999; + let existential_deposit = 1; // Account for the existential deposit add_network(netuid, 0, 0); - register_ok_neuron(netuid, delegate, coldkey, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); - // Make the delegate a delegate + // Make the account a delegate assert_ok!(SubtensorModule::become_delegate( - RuntimeOrigin::signed(coldkey), - delegate + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey )); - // Add balance to delegators - SubtensorModule::add_balance_to_coldkey_account(&delegator1, stake_amount1); - SubtensorModule::add_balance_to_coldkey_account(&delegator2, stake_amount2); - - // Delegate stakes + // Add stake from delegator1 + SubtensorModule::add_balance_to_coldkey_account(&delegator1, stake1); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(delegator1), - delegate, - stake_amount1 + delegate_hotkey, + stake1 )); + + // Add stake from delegator2 + SubtensorModule::add_balance_to_coldkey_account(&delegator2, stake2); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(delegator2), - delegate, - stake_amount2 + delegate_hotkey, + stake2 )); - // Check the total delegated stake + // Debug prints + println!("Delegator1 stake: {}", stake1); + println!("Delegator2 stake: {}", stake2); + println!("Existential deposit: {}", existential_deposit); + println!("Total stake for hotkey: {}", SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey)); + println!("Delegated stake for coldkey: {}", SubtensorModule::get_total_delegated_stake(&delegate_coldkey)); + + // Calculate expected total delegated stake + let expected_total_delegated = stake1 + stake2 - 2 * existential_deposit; + let actual_total_delegated = SubtensorModule::get_total_delegated_stake(&delegate_coldkey); + assert_eq!( - SubtensorModule::get_total_delegated_stake(&delegate), - stake_amount1 + stake_amount2 - 2 // Subtract 2 for existential deposits + actual_total_delegated, + expected_total_delegated, + "Total delegated stake should match the sum of delegators' stakes minus existential deposits. Expected: {}, Actual: {}", + expected_total_delegated, + actual_total_delegated ); }); } @@ -4350,44 +4394,327 @@ fn test_get_total_delegated_stake_multiple_delegators() { #[test] fn test_get_total_delegated_stake_exclude_owner_stake() { new_test_ext(1).execute_with(|| { - let delegate = U256::from(1); - let coldkey = U256::from(2); - let delegator = U256::from(3); - let owner_stake = 5000; - let delegator_stake = 1000; let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); + let delegator = U256::from(3); + let owner_stake = 1000; + let delegator_stake = 999; + let existential_deposit = 1; // Account for the existential deposit add_network(netuid, 0, 0); - register_ok_neuron(netuid, delegate, coldkey, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); - // Make the delegate a delegate + // Make the account a delegate assert_ok!(SubtensorModule::become_delegate( - RuntimeOrigin::signed(coldkey), - delegate + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey )); - // Add balance to owner and delegator - SubtensorModule::add_balance_to_coldkey_account(&coldkey, owner_stake); - SubtensorModule::add_balance_to_coldkey_account(&delegator, delegator_stake); - - // Owner adds stake + // Add owner stake + SubtensorModule::add_balance_to_coldkey_account(&delegate_coldkey, owner_stake); assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - delegate, + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey, owner_stake )); - // Delegator adds stake + // Add delegator stake + SubtensorModule::add_balance_to_coldkey_account(&delegator, delegator_stake); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(delegator), - delegate, + delegate_hotkey, delegator_stake )); + // Debug prints + println!("Owner stake: {}", owner_stake); + println!("Delegator stake: {}", delegator_stake); + println!("Existential deposit: {}", existential_deposit); + println!( + "Total stake for hotkey: {}", + SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey) + ); + println!( + "Delegated stake for coldkey: {}", + SubtensorModule::get_total_delegated_stake(&delegate_coldkey) + ); + // Check the total delegated stake (should exclude owner's stake) + let expected_delegated_stake = delegator_stake - existential_deposit; + let actual_delegated_stake = SubtensorModule::get_total_delegated_stake(&delegate_coldkey); + assert_eq!( - SubtensorModule::get_total_delegated_stake(&delegate), - delegator_stake - 1 // Subtract 1 for existential deposit + actual_delegated_stake, expected_delegated_stake, + "Delegated stake should exclude owner's stake. Expected: {}, Actual: {}", + expected_delegated_stake, actual_delegated_stake + ); + }); +} + +#[test] +fn test_do_schedule_coldkey_swap_subnet_owner_skips_min_balance() { + new_test_ext(1).execute_with(|| { + let netuid = 1u16; + let subnet_owner = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let current_block = 0u64; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey, subnet_owner, 0); + + // Make subnet_owner the owner of the subnet + SubnetOwner::::insert(netuid, subnet_owner); + + // Ensure subnet_owner has less than minimum balance + assert!( + SubtensorModule::get_coldkey_balance(&subnet_owner) + < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); + + // Generate valid PoW + let difficulty = U256::from(4) * U256::from(BaseDifficulty::::get()); + let (work, nonce) = generate_valid_pow(&subnet_owner, current_block, difficulty); + + // Debug prints + println!("Subnet owner: {:?}", subnet_owner); + println!("New coldkey: {:?}", new_coldkey); + println!("Current block: {}", current_block); + println!("Difficulty: {:?}", difficulty); + println!("Work: {:?}", work); + println!("Nonce: {}", nonce); + + // Verify the PoW + let seal = SubtensorModule::create_seal_hash(current_block, nonce, &subnet_owner); + println!("Calculated seal: {:?}", seal); + println!("Work matches seal: {}", work == seal); + println!( + "Seal meets difficulty: {}", + SubtensorModule::hash_meets_difficulty(&seal, difficulty) + ); + + // Attempt to schedule coldkey swap + let result = SubtensorModule::do_schedule_coldkey_swap( + &subnet_owner, + &new_coldkey, + work.to_fixed_bytes().to_vec(), + current_block, + nonce, + ); + + // Print the result + println!("Swap result: {:?}", result); + + assert_ok!(result); + + // Verify that the swap was scheduled + assert_eq!( + ColdkeySwapDestinations::::get(subnet_owner), + vec![new_coldkey] + ); + }); +} + +#[test] +fn test_do_schedule_coldkey_swap_delegate_with_500_tao_skips_min_balance() { + new_test_ext(1).execute_with(|| { + let netuid = 1u16; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); + let new_coldkey = U256::from(3); + let delegator = U256::from(4); + let current_block = 0u64; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); + + // Make delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey + )); + + // Add more than 500 TAO of stake to the delegate's hotkey + let stake_amount = 501_000_000_000; // 501 TAO in RAO + SubtensorModule::add_balance_to_coldkey_account(&delegator, stake_amount); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate_hotkey, + stake_amount + )); + + // Debug prints + println!( + "Delegator balance: {}", + SubtensorModule::get_coldkey_balance(&delegator) + ); + println!( + "Delegate coldkey balance: {}", + SubtensorModule::get_coldkey_balance(&delegate_coldkey) + ); + println!("Stake amount: {}", stake_amount); + println!( + "Delegate hotkey total stake: {}", + SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey) + ); + println!( + "Delegate coldkey delegated stake: {}", + SubtensorModule::get_total_delegated_stake(&delegate_coldkey) + ); + + // Ensure delegate's coldkey has less than minimum balance + assert!( + SubtensorModule::get_coldkey_balance(&delegate_coldkey) + < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + "Delegate coldkey balance should be less than minimum required" + ); + + // Ensure the delegate's hotkey has more than 500 TAO delegated + assert!( + SubtensorModule::get_total_delegated_stake(&delegate_coldkey) >= 500_000_000_000, + "Delegate hotkey should have at least 500 TAO delegated" + ); + + // Generate valid PoW + let (work, nonce) = generate_valid_pow( + &delegate_coldkey, + current_block, + U256::from(4) * U256::from(BaseDifficulty::::get()), + ); + + // Debug prints + println!("Work: {:?}", work); + println!("Nonce: {}", nonce); + + // Attempt to schedule coldkey swap + let result = SubtensorModule::do_schedule_coldkey_swap( + &delegate_coldkey, + &new_coldkey, + work.to_fixed_bytes().to_vec(), + current_block, + nonce, + ); + + // Print the result + println!("Swap result: {:?}", result); + + assert_ok!(result); + + // Verify that the swap was scheduled + assert_eq!( + ColdkeySwapDestinations::::get(delegate_coldkey), + vec![new_coldkey] + ); + + // Additional debug prints after swap + println!( + "Coldkey swap destinations: {:?}", + ColdkeySwapDestinations::::get(delegate_coldkey) + ); + println!( + "Is coldkey in arbitration: {}", + SubtensorModule::coldkey_in_arbitration(&delegate_coldkey) + ); + }); +} + +#[test] +fn test_do_schedule_coldkey_swap_regular_user_fails_min_balance() { + new_test_ext(1).execute_with(|| { + let netuid = 1u16; + let regular_user = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let current_block = 0u64; + let nonce = 0u64; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey, regular_user, 0); + + // Ensure regular_user has less than minimum balance + assert!( + SubtensorModule::get_coldkey_balance(®ular_user) + < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); + + let (work, _) = generate_valid_pow( + ®ular_user, + current_block, + U256::from(4) * U256::from(BaseDifficulty::::get()), + ); + + // Attempt to schedule coldkey swap + assert_noop!( + SubtensorModule::do_schedule_coldkey_swap( + ®ular_user, + &new_coldkey, + work.to_fixed_bytes().to_vec(), + current_block, + nonce + ), + Error::::InsufficientBalanceToPerformColdkeySwap + ); + + // Verify that the swap was not scheduled + assert!(ColdkeySwapDestinations::::get(regular_user).is_empty()); + }); +} + +#[test] +fn test_do_schedule_coldkey_swap_regular_user_passes_min_balance() { + new_test_ext(1).execute_with(|| { + let netuid = 1u16; + let regular_user = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let current_block = 0u64; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey, regular_user, 0); + + // Ensure regular_user has more than minimum balance + SubtensorModule::add_balance_to_coldkey_account( + ®ular_user, + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + 1, + ); + assert!( + SubtensorModule::get_coldkey_balance(®ular_user) + > MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + ); + + // Generate valid PoW + let (work, nonce) = generate_valid_pow( + ®ular_user, + current_block, + U256::from(4) * U256::from(BaseDifficulty::::get()), + ); + + // Debug prints + println!("Regular user: {:?}", regular_user); + println!("New coldkey: {:?}", new_coldkey); + println!("Current block: {}", current_block); + println!("Work: {:?}", work); + println!("Nonce: {}", nonce); + + // Attempt to schedule coldkey swap + let result = SubtensorModule::do_schedule_coldkey_swap( + ®ular_user, + &new_coldkey, + work.to_fixed_bytes().to_vec(), + current_block, + nonce, + ); + + // Print the result + println!("Swap result: {:?}", result); + + assert_ok!(result); + + // Verify that the swap was scheduled + assert_eq!( + ColdkeySwapDestinations::::get(regular_user), + vec![new_coldkey] ); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9be039e6b..f020c5183 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -315,6 +315,7 @@ impl Contains for SafeModeWhitelistedCalls { pallet_subtensor::Call::schedule_coldkey_swap { .. } | pallet_subtensor::Call::set_weights { .. } | pallet_subtensor::Call::set_root_weights { .. } + | pallet_subtensor::Call::serve_axon { .. } ) ) } From e30c8f4599573159297a48253eb3359d4a3337c9 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 18:52:02 +0400 Subject: [PATCH 126/185] feat: coldkey swap info --- node/src/rpc.rs | 1 + pallets/subtensor/rpc/src/lib.rs | 68 ++++++- pallets/subtensor/runtime-api/src/lib.rs | 6 + pallets/subtensor/src/lib.rs | 1 + .../src/schedule_coldkey_swap_info.rs | 172 ++++++++++++++++++ pallets/subtensor/src/swap.rs | 18 +- pallets/subtensor/tests/staking.rs | 104 +++++------ runtime/src/lib.rs | 17 ++ 8 files changed, 325 insertions(+), 62 deletions(-) create mode 100644 pallets/subtensor/src/schedule_coldkey_swap_info.rs diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 54f82447f..c169b8530 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -60,6 +60,7 @@ where C::Api: subtensor_custom_rpc_runtime_api::NeuronInfoRuntimeApi, C::Api: subtensor_custom_rpc_runtime_api::SubnetInfoRuntimeApi, C::Api: subtensor_custom_rpc_runtime_api::SubnetRegistrationRuntimeApi, + C::Api: subtensor_custom_rpc_runtime_api::ColdkeySwapRuntimeApi, B: sc_client_api::Backend + Send + Sync + 'static, P: TransactionPool + 'static, { diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 2f71e9c21..15157be2f 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use sp_api::ProvideRuntimeApi; pub use subtensor_custom_rpc_runtime_api::{ - DelegateInfoRuntimeApi, NeuronInfoRuntimeApi, SubnetInfoRuntimeApi, + ColdkeySwapRuntimeApi, DelegateInfoRuntimeApi, NeuronInfoRuntimeApi, SubnetInfoRuntimeApi, SubnetRegistrationRuntimeApi, }; @@ -51,6 +51,24 @@ pub trait SubtensorCustomApi { #[method(name = "subnetInfo_getLockCost")] fn get_network_lock_cost(&self, at: Option) -> RpcResult; + #[method(name = "coldkeySwap_getScheduledColdkeySwap")] + fn get_scheduled_coldkey_swap( + &self, + coldkey_account_vec: Vec, + at: Option, + ) -> RpcResult>; + #[method(name = "coldkeySwap_getRemainingArbitrationPeriod")] + fn get_remaining_arbitration_period( + &self, + coldkey_account_vec: Vec, + at: Option, + ) -> RpcResult>; + #[method(name = "coldkeySwap_getColdkeySwapDestinations")] + fn get_coldkey_swap_destinations( + &self, + coldkey_account_vec: Vec, + at: Option, + ) -> RpcResult>; } pub struct SubtensorCustom { @@ -99,6 +117,7 @@ where C::Api: NeuronInfoRuntimeApi, C::Api: SubnetInfoRuntimeApi, C::Api: SubnetRegistrationRuntimeApi, + C::Api: ColdkeySwapRuntimeApi, { fn get_delegates(&self, at: Option<::Hash>) -> RpcResult> { let api = self.client.runtime_api(); @@ -223,4 +242,51 @@ where Error::RuntimeError(format!("Unable to get subnet lock cost: {:?}", e)).into() }) } + + fn get_scheduled_coldkey_swap( + &self, + coldkey_account_vec: Vec, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_scheduled_coldkey_swap(at, coldkey_account_vec) + .map_err(|e| { + Error::RuntimeError(format!("Unable to get scheduled coldkey swap: {:?}", e)).into() + }) + } + + fn get_remaining_arbitration_period( + &self, + coldkey_account_vec: Vec, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_remaining_arbitration_period(at, coldkey_account_vec) + .map_err(|e| { + Error::RuntimeError(format!( + "Unable to get remaining arbitration period: {:?}", + e + )) + .into() + }) + } + + fn get_coldkey_swap_destinations( + &self, + coldkey_account_vec: Vec, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_coldkey_swap_destinations(at, coldkey_account_vec) + .map_err(|e| { + Error::RuntimeError(format!("Unable to get coldkey swap destinations: {:?}", e)) + .into() + }) + } } diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 9095ad54a..a647d3619 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -32,4 +32,10 @@ sp_api::decl_runtime_apis! { pub trait SubnetRegistrationRuntimeApi { fn get_network_registration_cost() -> u64; } + + pub trait ColdkeySwapRuntimeApi { + fn get_scheduled_coldkey_swap( coldkey_account_vec: Vec ) -> Vec; + fn get_remaining_arbitration_period( coldkey_account_vec: Vec ) -> Vec; + fn get_coldkey_swap_destinations( coldkey_account_vec: Vec ) -> Vec; + } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 787989f17..a1abff24c 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -51,6 +51,7 @@ mod weights; pub mod delegate_info; pub mod neuron_info; +pub mod schedule_coldkey_swap_info; pub mod stake_info; pub mod subnet_info; diff --git a/pallets/subtensor/src/schedule_coldkey_swap_info.rs b/pallets/subtensor/src/schedule_coldkey_swap_info.rs new file mode 100644 index 000000000..0408cc835 --- /dev/null +++ b/pallets/subtensor/src/schedule_coldkey_swap_info.rs @@ -0,0 +1,172 @@ +use super::*; +use codec::Compact; +use frame_support::pallet_prelude::{Decode, Encode}; +use sp_core::hexdisplay::AsBytesRef; + +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] +pub struct ScheduledColdkeySwapInfo { + old_coldkey: T::AccountId, + new_coldkey: T::AccountId, + arbitration_block: Compact, +} + +impl Pallet { + /// Retrieves the scheduled coldkey swap information for an existing account. + /// + /// # Arguments + /// + /// * `coldkey` - The account ID of the coldkey to check. + /// + /// # Returns + /// + /// * `Option>` - The scheduled coldkey swap information if it exists, otherwise `None`. + /// + /// # Notes + /// + /// This function checks if there are any destination coldkeys associated with the given coldkey. + /// If there are, it retrieves the arbitration block and constructs the `ScheduledColdkeySwapInfo` struct. + fn get_scheduled_coldkey_swap_by_existing_account( + coldkey: AccountIdOf, + ) -> Option> { + let destinations: Vec = ColdkeySwapDestinations::::get(&coldkey); + if destinations.is_empty() { + return None; + } + + let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(&coldkey); + + Some(ScheduledColdkeySwapInfo { + old_coldkey: coldkey, + new_coldkey: destinations[0].clone(), + arbitration_block: arbitration_block.into(), + }) + } + + /// Retrieves the scheduled coldkey swap information for a given coldkey account vector. + /// + /// # Arguments + /// + /// * `coldkey_account_vec` - The vector of bytes representing the coldkey account. + /// + /// # Returns + /// + /// * `Option>` - The scheduled coldkey swap information if it exists, otherwise `None`. + /// + /// # Notes + /// + /// This function decodes the coldkey account vector into an account ID and then calls + /// `get_scheduled_coldkey_swap_by_existing_account` to retrieve the swap information. + pub fn get_scheduled_coldkey_swap( + coldkey_account_vec: Vec, + ) -> Option> { + if coldkey_account_vec.len() != 32 { + return None; + } + + let coldkey: AccountIdOf = + T::AccountId::decode(&mut coldkey_account_vec.as_bytes_ref()).ok()?; + Self::get_scheduled_coldkey_swap_by_existing_account(coldkey) + } + + /// Retrieves all scheduled coldkey swaps from storage. + /// + /// # Returns + /// + /// * `Vec>` - A vector containing all scheduled coldkey swap information. + /// + /// # Notes + /// + /// This function iterates over all coldkeys in `ColdkeySwapDestinations` and retrieves their swap information + /// using `get_scheduled_coldkey_swap_by_existing_account`. + pub fn get_all_scheduled_coldkey_swaps() -> Vec> { + let mut scheduled_swaps: Vec> = Vec::new(); + for coldkey in ColdkeySwapDestinations::::iter_keys() { + if let Some(swap_info) = Self::get_scheduled_coldkey_swap_by_existing_account(coldkey) { + scheduled_swaps.push(swap_info); + } + } + scheduled_swaps + } + + /// Retrieves the scheduled coldkey swaps for a given block. + /// + /// # Arguments + /// + /// * `block` - The block number to check for scheduled coldkey swaps. + /// + /// # Returns + /// + /// * `Vec>` - A vector containing the scheduled coldkey swap information for the given block. + /// + /// # Notes + /// + /// This function retrieves the coldkeys to swap at the given block and then retrieves their swap information + /// using `get_scheduled_coldkey_swap_by_existing_account`. + pub fn get_scheduled_coldkey_swaps_at_block(block: u64) -> Vec> { + let coldkeys_to_swap: Vec = ColdkeysToSwapAtBlock::::get(block); + let mut scheduled_swaps: Vec> = Vec::new(); + for coldkey in coldkeys_to_swap { + if let Some(swap_info) = Self::get_scheduled_coldkey_swap_by_existing_account(coldkey) { + scheduled_swaps.push(swap_info); + } + } + scheduled_swaps + } + + /// Retrieves the remaining arbitration period for a given coldkey account vector. + /// + /// # Arguments + /// + /// * `coldkey_account_vec` - The vector of bytes representing the coldkey account. + /// + /// # Returns + /// + /// * `Option` - The remaining arbitration period in blocks if it exists, otherwise `None`. + /// + /// # Notes + /// + /// This function decodes the coldkey account vector into an account ID and calculates the remaining arbitration period + /// by subtracting the current block number from the arbitration block number. + pub fn get_remaining_arbitration_period(coldkey_account_vec: Vec) -> Option { + if coldkey_account_vec.len() != 32 { + return None; + } + + let coldkey: AccountIdOf = + T::AccountId::decode(&mut coldkey_account_vec.as_bytes_ref()).ok()?; + let current_block: u64 = Self::get_current_block_as_u64(); + let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(&coldkey); + + if arbitration_block > current_block { + Some(arbitration_block.saturating_sub(current_block)) + } else { + Some(0) + } + } + + /// Retrieves the destination coldkeys for a given coldkey account vector. + /// + /// # Arguments + /// + /// * `coldkey_account_vec` - The vector of bytes representing the coldkey account. + /// + /// # Returns + /// + /// * `Option>` - A vector containing the destination coldkeys if they exist, otherwise `None`. + /// + /// # Notes + /// + /// This function decodes the coldkey account vector into an account ID and retrieves the destination coldkeys + /// from `ColdkeySwapDestinations`. + pub fn get_coldkey_swap_destinations( + coldkey_account_vec: Vec, + ) -> Option> { + if coldkey_account_vec.len() != 32 { + return None; + } + + let coldkey: AccountIdOf = + T::AccountId::decode(&mut coldkey_account_vec.as_bytes_ref()).ok()?; + Some(ColdkeySwapDestinations::::get(&coldkey)) + } +} diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 7a548f289..da4edc656 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -189,15 +189,15 @@ impl Pallet { /// /// This function calculates the remaining arbitration period by subtracting the current block number /// from the arbitration block number of the coldkey. - pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { - let current_block: u64 = Self::get_current_block_as_u64(); - let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); - if arbitration_block > current_block { - arbitration_block.saturating_sub(current_block) - } else { - 0 - } - } + // pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { + // let current_block: u64 = Self::get_current_block_as_u64(); + // let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); + // if arbitration_block > current_block { + // arbitration_block.saturating_sub(current_block) + // } else { + // 0 + // } + // } pub fn meets_min_allowed_coldkey_balance(coldkey: &T::AccountId) -> bool { let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 873c81309..b275e5f0c 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3788,58 +3788,58 @@ fn test_concurrent_arbitrated_coldkey_swaps() { }); } -#[test] -fn test_get_remaining_arbitration_period() { - new_test_ext(1).execute_with(|| { - let coldkey_account_id = U256::from(12345); // arbitrary coldkey - let new_coldkey_account_id = U256::from(54321); // arbitrary new coldkey - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - &coldkey_account_id, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - // Schedule a coldkey swap to set the arbitration block - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id.clone(), - &new_coldkey_account_id, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - - // Get the current block number and arbitration period - let current_block: u64 = SubtensorModule::get_current_block_as_u64(); - let arbitration_period: u64 = ArbitrationPeriod::::get(); - log::info!("arbitration_period: {:?}", arbitration_period); - let arbitration_block: u64 = current_block + arbitration_period; - log::info!("arbitration_block: {:?}", arbitration_block); - - // Check if the remaining arbitration period is correct - let remaining_period = - SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); - assert_eq!(remaining_period, arbitration_period); - - // Move the current block forward and check again - step_block(50); - let remaining_period = - SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); - assert_eq!(remaining_period, arbitration_period - 50); - - // Move the current block beyond the arbitration block and check again - step_block((arbitration_period as u16) - 50 + 1); - let remaining_period = - SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); - assert_eq!(remaining_period, 0); - }); -} +// #[test] +// fn test_get_remaining_arbitration_period() { +// new_test_ext(1).execute_with(|| { +// let coldkey_account_id = U256::from(12345); // arbitrary coldkey +// let new_coldkey_account_id = U256::from(54321); // arbitrary new coldkey + +// let current_block = SubtensorModule::get_current_block_as_u64(); +// let (work, nonce) = generate_valid_pow( +// &coldkey_account_id, +// current_block, +// U256::from(BaseDifficulty::::get()), +// ); + +// SubtensorModule::add_balance_to_coldkey_account( +// &coldkey_account_id, +// MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, +// ); + +// // Schedule a coldkey swap to set the arbitration block +// assert_ok!(SubtensorModule::do_schedule_coldkey_swap( +// &coldkey_account_id.clone(), +// &new_coldkey_account_id, +// work.to_fixed_bytes().to_vec(), +// current_block, +// nonce +// )); + +// // Get the current block number and arbitration period +// let current_block: u64 = SubtensorModule::get_current_block_as_u64(); +// let arbitration_period: u64 = ArbitrationPeriod::::get(); +// log::info!("arbitration_period: {:?}", arbitration_period); +// let arbitration_block: u64 = current_block + arbitration_period; +// log::info!("arbitration_block: {:?}", arbitration_block); + +// // Check if the remaining arbitration period is correct +// let remaining_period = +// SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); +// assert_eq!(remaining_period, arbitration_period); + +// // Move the current block forward and check again +// step_block(50); +// let remaining_period = +// SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); +// assert_eq!(remaining_period, arbitration_period - 50); + +// // Move the current block beyond the arbitration block and check again +// step_block((arbitration_period as u16) - 50 + 1); +// let remaining_period = +// SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); +// assert_eq!(remaining_period, 0); +// }); +// } #[test] fn test_transfer_coldkey_in_arbitration() { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f020c5183..f0d830c38 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1663,6 +1663,23 @@ impl_runtime_apis! { SubtensorModule::get_network_lock_cost() } } + + impl subtensor_custom_rpc_runtime_api::ColdkeySwapRuntimeApi for Runtime { + fn get_scheduled_coldkey_swap( coldkey_account_vec: Vec ) -> Vec { + let result = SubtensorModule::get_scheduled_coldkey_swap( coldkey_account_vec ); + result.encode() + } + + fn get_remaining_arbitration_period( coldkey_account_vec: Vec ) -> Vec { + let result = SubtensorModule::get_remaining_arbitration_period( coldkey_account_vec ); + result.encode() + } + + fn get_coldkey_swap_destinations( coldkey_account_vec: Vec ) -> Vec { + let result = SubtensorModule::get_coldkey_swap_destinations( coldkey_account_vec ); + result.encode() + } + } } // #[cfg(test)] From 7aaeb090deddbeb98e8baf1740016afc021e240c Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 21:52:40 +0400 Subject: [PATCH 127/185] feat: move back to on_initialise, lints --- pallets/subtensor/src/delegate_info.rs | 4 +- pallets/subtensor/src/lib.rs | 48 +++++++------ .../src/schedule_coldkey_swap_info.rs | 6 +- pallets/subtensor/src/staking.rs | 71 ------------------- pallets/subtensor/src/swap.rs | 22 +++--- pallets/subtensor/tests/staking.rs | 1 + runtime/src/lib.rs | 2 +- 7 files changed, 48 insertions(+), 106 deletions(-) diff --git a/pallets/subtensor/src/delegate_info.rs b/pallets/subtensor/src/delegate_info.rs index 7ae50355a..e1eab1210 100644 --- a/pallets/subtensor/src/delegate_info.rs +++ b/pallets/subtensor/src/delegate_info.rs @@ -142,7 +142,7 @@ impl Pallet { for (delegator, stake) in Stake::::iter_prefix(&hotkey) { if delegator != owner { - total_delegated += stake; + total_delegated = total_delegated.saturating_add(stake); } } } @@ -162,7 +162,7 @@ impl Pallet { // Iterate through all delegators for this hotkey for (delegator, stake) in Stake::::iter_prefix(hotkey) { if delegator != Self::get_coldkey_for_hotkey(hotkey) { - total_delegated += stake; + total_delegated = total_delegated.saturating_add(stake); } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index a1abff24c..6fdc7cc6e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1343,40 +1343,48 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_idle(_n: BlockNumberFor, remaining_weight: Weight) -> Weight { - // Unstake and transfer pending coldkeys up to the remaining weight - match Self::swap_coldkeys_this_block(&remaining_weight) { + fn on_idle(_n: BlockNumberFor, _remaining_weight: Weight) -> Weight { + Weight::zero() + } + + fn on_initialize(_block_number: BlockNumberFor) -> Weight { + let mut total_weight = Weight::zero(); + + // Create a Weight::MAX value to pass to swap_coldkeys_this_block + let max_weight = Weight::MAX; + + // Perform coldkey swapping + let swap_weight = match Self::swap_coldkeys_this_block(&max_weight) { Ok(weight_used) => weight_used, Err(e) => { log::error!("Error while swapping coldkeys: {:?}", e); - Weight::default() + Weight::zero() } - } - } + }; + total_weight = total_weight.saturating_add(swap_weight); - // ---- Called on the initialization of this pallet. (the order of on_finalize calls is determined in the runtime) - // - // # Args: - // * 'n': (BlockNumberFor): - // - The number of the block we are initializing. - fn on_initialize(_block_number: BlockNumberFor) -> Weight { + // Perform block step let block_step_result = Self::block_step(); match block_step_result { Ok(_) => { - // --- If the block step was successful, return the weight. log::debug!("Successfully ran block step."); - Weight::from_parts(110_634_229_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(8304_u64)) - .saturating_add(T::DbWeight::get().writes(110_u64)) + total_weight = total_weight.saturating_add( + Weight::from_parts(110_634_229_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(8304_u64)) + .saturating_add(T::DbWeight::get().writes(110_u64)), + ); } Err(e) => { - // --- If the block step was unsuccessful, return the weight anyway. log::error!("Error while stepping block: {:?}", e); - Weight::from_parts(110_634_229_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(8304_u64)) - .saturating_add(T::DbWeight::get().writes(110_u64)) + total_weight = total_weight.saturating_add( + Weight::from_parts(110_634_229_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(8304_u64)) + .saturating_add(T::DbWeight::get().writes(110_u64)), + ); } } + + total_weight } fn on_runtime_upgrade() -> frame_support::weights::Weight { diff --git a/pallets/subtensor/src/schedule_coldkey_swap_info.rs b/pallets/subtensor/src/schedule_coldkey_swap_info.rs index 0408cc835..e23ad23b6 100644 --- a/pallets/subtensor/src/schedule_coldkey_swap_info.rs +++ b/pallets/subtensor/src/schedule_coldkey_swap_info.rs @@ -1,6 +1,7 @@ use super::*; use codec::Compact; use frame_support::pallet_prelude::{Decode, Encode}; + use sp_core::hexdisplay::AsBytesRef; #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] @@ -37,7 +38,10 @@ impl Pallet { Some(ScheduledColdkeySwapInfo { old_coldkey: coldkey, - new_coldkey: destinations[0].clone(), + new_coldkey: destinations.first().cloned().unwrap_or_else(|| { + T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("Infinite length input; no invalid inputs for type; qed") + }), arbitration_block: arbitration_block.into(), }) } diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 3499bdda5..199234a30 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -868,75 +868,4 @@ impl Pallet { Self::add_balance_to_coldkey_account(&delegate_coldkey_i, stake_i); } } - - // pub fn x( coldkey_a: &T::AccountId, coldkey_b: &T::AccountId ) -> Weight { - // let mut weight = frame_support::weights::Weight::from_parts(0, 0); - - // // Get the hotkeys associated with coldkey_a. - // let coldkey_a_hotkeys: Vec = StakingHotkeys::::get( &coldkey_a ); - // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - // // Iterate over all the hotkeys associated with this coldkey - // for hotkey_i in coldkey_a_hotkeys.iter() { - - // // Get the current stake from coldkey_a to hotkey_i. - // let all_current_stake_i: u64 = Self::get_stake_for_coldkey_and_hotkey( &coldkey_a, &hotkey_i ); - // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - // // We remove the balance from the hotkey acount equal to all of it. - // Self::decrease_stake_on_coldkey_hotkey_account( &coldkey_a, &hotkey_i, all_current_stake_i ); - // weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 4)); - - // // We add the balance to the coldkey. If the above fails we will not credit this coldkey. - // Self::add_balance_to_coldkey_account( &coldkey_a, all_current_stake_i ); - // weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); - // } - // Unstake all delegate stake make by this coldkey to non-owned hotkeys - // let staking_hotkeys = StakingHotkeys::::get(¤t_coldkey); - - // // iterate over all staking hotkeys. - // for hotkey in staking_hotkeys { - // // Get the current stake - // let current_stake: u64 = - // Self::get_stake_for_coldkey_and_hotkey(¤t_coldkey, &hotkey); - - // // Unstake all balance if there's any stake - // if current_stake > 0 { - // Self::do_remove_stake( - // RawOrigin::Signed(current_coldkey.clone()).into(), - // hotkey.clone(), - // current_stake, - // )?; - // } - // } - - // let total_balance = Self::get_coldkey_balance(¤t_coldkey); - // log::info!("Total Bank Balance: {:?}", total_balance); - - // // Get the total balance here. - // let total_balance = Self::get_coldkey_balance( &coldkey_a ); - // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0)); - - // if !total_balance.is_zero() { - // // Attempt to transfer the entire total balance to coldkey_b. - // let _ = T::Currency::transfer( - // coldkey_a, - // coldkey_b, - // total_balance, - // Preservation::Expendable, - // ); - // weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - // } - - // // Emit event - // Self::deposit_event( - // Event::AllBalanceUnstakedAndTransferredToNewColdkey { - // current_coldkey: coldkey_a.clone(), - // new_coldkey: coldkey_b.clone(), - // total_balance - // } - // ); - - // weight - // } } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index da4edc656..24d67aeb4 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -203,9 +203,11 @@ impl Pallet { let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); let mut total_staking_balance: u64 = 0; for hotkey in all_staked_keys { - total_staking_balance += Self::get_stake_for_coldkey_and_hotkey(coldkey, &hotkey); + total_staking_balance = total_staking_balance + .saturating_add(Self::get_stake_for_coldkey_and_hotkey(coldkey, &hotkey)); } - total_staking_balance += Self::get_coldkey_balance(coldkey); + total_staking_balance = + total_staking_balance.saturating_add(Self::get_coldkey_balance(coldkey)); total_staking_balance >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP } @@ -414,15 +416,13 @@ impl Pallet { // Swap the coldkey. let total_balance: u64 = Self::get_coldkey_balance(old_coldkey); if total_balance > 0 { - // Attempt to transfer the entire total balance to coldkeyB. - if let Err(e) = T::Currency::transfer( + // Attempt to transfer the entire total balance to new_coldkey. + T::Currency::transfer( old_coldkey, new_coldkey, total_balance, Preservation::Expendable, - ) { - return Err(e.into()); - } + )?; } Ok(weight) @@ -832,7 +832,7 @@ impl Pallet { Owner::::insert(hotkey, new_coldkey); // Update the transaction weight - *weight += T::DbWeight::get().reads_writes(2, 2); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } } @@ -858,18 +858,18 @@ impl Pallet { ); // Update the transaction weight - *weight += T::DbWeight::get().reads_writes(2, 2); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } // Update the list of owned hotkeys for both old and new coldkeys OwnedHotkeys::::remove(old_coldkey); OwnedHotkeys::::insert(new_coldkey, old_owned_hotkeys); - *weight += T::DbWeight::get().reads_writes(1, 2); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Update the staking hotkeys for both old and new coldkeys let staking_hotkeys: Vec = StakingHotkeys::::take(old_coldkey); StakingHotkeys::::insert(new_coldkey, staking_hotkeys); - *weight += T::DbWeight::get().reads_writes(1, 1); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); // Log the total stake of old and new coldkeys after the swap log::info!( diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index b275e5f0c..fdbc2521d 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,4 +1,5 @@ #![allow(clippy::unwrap_used)] +#![allow(clippy::arithmetic_side_effects)] use frame_support::pallet_prelude::{ InvalidTransaction, TransactionValidity, TransactionValidityError, diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f0d830c38..a58bff272 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 189, + spec_version: 187, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From b43bed6f0834ba307b64b2654d2722f396131ba3 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 22:37:13 +0400 Subject: [PATCH 128/185] draft: hotkey swap for senate --- pallets/subtensor/src/swap.rs | 14 +++++ pallets/subtensor/tests/swap.rs | 93 +++++++++++++++++++++++++-------- 2 files changed, 85 insertions(+), 22 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 24d67aeb4..2073aa5e2 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -76,6 +76,7 @@ impl Pallet { Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); + Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; Self::set_last_tx_block(&coldkey, block); weight.saturating_accrue(T::DbWeight::get().writes(1)); @@ -948,4 +949,17 @@ impl Pallet { } weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } + + /// Swaps the Senate membership from the old hotkey to the new hotkey if applicable. + pub fn swap_senate_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + if T::SenateMembers::is_member(old_hotkey) { + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + Ok(()) + } } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 90ebdfdcb..7445470f8 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -2,7 +2,7 @@ use codec::Encode; use frame_support::weights::Weight; -use frame_support::{assert_err, assert_ok}; +use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::Config; mod mock; use mock::*; @@ -1666,24 +1666,73 @@ fn test_coldkey_swap_total() { }); } -// #[test] -// fn test_coldkey_arbitrated_sw() { -// new_test_ext(1).execute_with(|| { -// let coldkey = U256::from(1); -// let hotkey = U256::from(2); -// let netuid = 1u16; - -// // Setup initial state -// add_network(netuid, 13, 0); -// register_ok_neuron(netuid, hotkey, coldkey, 0); - -// // Check if coldkey has associated hotkeys -// assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); - -// // Check for a coldkey without associated hotkeys -// let unassociated_coldkey = U256::from(3); -// assert!(!SubtensorModule::coldkey_has_associated_hotkeys( -// &unassociated_coldkey -// )); -// }); -// } +#[test] +fn test_swap_senate_member() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let non_member_hotkey = U256::from(3); + let mut weight = Weight::zero(); + + // Setup: Add old_hotkey as a Senate member + assert_ok!(Senate::set_members( + RuntimeOrigin::root(), + vec![old_hotkey], + None, + 0 + )); + + // Test 1: Successful swap + assert_ok!(SubtensorModule::swap_senate_member( + &old_hotkey, + &new_hotkey, + &mut weight + )); + assert!(Senate::is_member(&new_hotkey)); + assert!(!Senate::is_member(&old_hotkey)); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(2, 2); + assert_eq!(weight, expected_weight); + + // Reset weight for next test + weight = Weight::zero(); + + // Test 2: Swap with non-member (should not change anything) + assert_ok!(SubtensorModule::swap_senate_member( + &non_member_hotkey, + &new_hotkey, + &mut weight + )); + assert!(Senate::is_member(&new_hotkey)); + assert!(!Senate::is_member(&non_member_hotkey)); + + // Verify weight update (should only have read operations) + let expected_weight = ::DbWeight::get().reads(1); + assert_eq!(weight, expected_weight); + + // Reset weight for next test + weight = Weight::zero(); + + // Test 3: Setup for swap to an existing member + let another_member = U256::from(4); + assert_ok!(Senate::set_members( + RuntimeOrigin::root(), + vec![new_hotkey, another_member], + None, + 1 + )); + + // Test 4: Setup for swap with maximum members reached + let mut members = vec![new_hotkey, another_member]; + for i in 5..=SenateMaxMembers::get() { + members.push(U256::from(i as u64)); + } + assert_ok!(Senate::set_members( + RuntimeOrigin::root(), + members.clone(), + None, + 2 + )); + }); +} From 3817b766ef2247e564fb5550749fae41bd57aecf Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 23:55:11 +0400 Subject: [PATCH 129/185] chore: bump arb delay back to 3 days --- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/tests/staking.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6fdc7cc6e..a74b3956d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -411,7 +411,7 @@ pub mod pallet { /// This value represents the default arbitration period in blocks. /// The period is set to 18 hours, assuming a block time of 12 seconds. pub fn DefaultArbitrationPeriod() -> u64 { - 5400 // 18 hours * 60 minutes/hour * 5 blocks/minute + 7200 * 3 // 3 days } #[pallet::storage] // ---- StorageItem Global Used Work. pub type ArbitrationPeriod = diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index fdbc2521d..5db439e5b 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3192,7 +3192,7 @@ fn test_arbitrated_coldkey_swap_success() { ); // Check that drain block is set correctly - let drain_block: u64 = 5400 + 1; + let drain_block: u64 = 7200 * 3 + 1; log::info!( "ColdkeysToSwapAtBlock before scheduling: {:?}", @@ -3399,7 +3399,7 @@ fn test_arbitrated_coldkey_swap_with_no_stake() { )); // Make 5400 blocks pass, simulating on_idle for each block - let drain_block: u64 = 5400 + 1; + let drain_block: u64 = 7200 * 3 + 1; for _ in 0..drain_block { next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3463,7 +3463,7 @@ fn test_arbitrated_coldkey_swap_with_multiple_stakes() { )); // Make 5400 blocks pass, simulating on_idle for each block - let drain_block: u64 = 5400 + 1; + let drain_block: u64 = 7200 * 3 + 1; for _ in 0..drain_block { next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3548,7 +3548,7 @@ fn test_arbitrated_coldkey_swap_multiple_arbitrations() { ); // Simulate the passage of blocks and on_idle calls - for i in 0..(5400 + 1) { + for i in 0..(7200 * 3 + 1) { next_block(); SubtensorModule::on_idle(System::block_number(), Weight::MAX); @@ -3771,7 +3771,7 @@ fn test_concurrent_arbitrated_coldkey_swaps() { nonce2 )); // Make 5400 blocks pass - let drain_block: u64 = 5400 + 1; + let drain_block: u64 = 7200 * 3 + 1; run_to_block(drain_block); // Run arbitration From 380aa6508307b2cb8b949c032320a33db26bdcfc Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 9 Jul 2024 23:59:26 +0400 Subject: [PATCH 130/185] chore: reduce min tao req to 0.1 --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index a74b3956d..dd3f1a4c1 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -86,7 +86,7 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); /// Minimum balance required to perform a coldkey swap - pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 1_000_000_000; // 1 TAO in RAO + pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 1_000_000_00; // 0.1 TAO in RAO #[pallet::pallet] #[pallet::without_storage_info] From fec59cb1f3e90290bb1528a99fe234cb7b178c03 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 10 Jul 2024 00:19:28 +0400 Subject: [PATCH 131/185] feat: whitelist set_commitment --- runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a58bff272..dca8cfe98 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -317,6 +317,7 @@ impl Contains for SafeModeWhitelistedCalls { | pallet_subtensor::Call::set_root_weights { .. } | pallet_subtensor::Call::serve_axon { .. } ) + | RuntimeCall::Commitments(pallet_commitments::Call::set_commitment { .. }) ) } } From 9de2afba8e1cff8000f4fb518eaffafc4d952ab0 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 10 Jul 2024 00:44:11 +0400 Subject: [PATCH 132/185] spec verion , lints --- pallets/subtensor/src/lib.rs | 2 +- runtime/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index dd3f1a4c1..0560ca72c 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -86,7 +86,7 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); /// Minimum balance required to perform a coldkey swap - pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 1_000_000_00; // 0.1 TAO in RAO + pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 100_000_000; // 0.1 TAO in RAO #[pallet::pallet] #[pallet::without_storage_info] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index dca8cfe98..3e5af1b43 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 187, + spec_version: 194, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From dc96aad8bc7d21638ef957cf0e4cc4732cff49b1 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 10 Jul 2024 13:46:36 -0400 Subject: [PATCH 133/185] fix clippy issue --- pallets/subtensor/src/root.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index ad764dc6a..e1bab02e0 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -577,7 +577,11 @@ impl Pallet { } // --- 15. Update the registration counters for both the block and interval. + #[allow(clippy::arithmetic_side_effects)] + // note this RA + clippy false positive is a known substrate issue RegistrationsThisInterval::::mutate(root_netuid, |val| *val += 1); + #[allow(clippy::arithmetic_side_effects)] + // note this RA + clippy false positive is a known substrate issue RegistrationsThisBlock::::mutate(root_netuid, |val| *val += 1); // --- 16. Log and announce the successful registration. From 0abfb9b3f7743d9170894c43b118730eff1a1025 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 15:55:31 +0400 Subject: [PATCH 134/185] chore: remove coldkey swap rpc --- node/src/rpc.rs | 1 - pallets/subtensor/rpc/src/lib.rs | 68 +------ pallets/subtensor/runtime-api/src/lib.rs | 6 - pallets/subtensor/src/lib.rs | 1 - .../src/schedule_coldkey_swap_info.rs | 176 ------------------ runtime/src/lib.rs | 17 -- 6 files changed, 1 insertion(+), 268 deletions(-) delete mode 100644 pallets/subtensor/src/schedule_coldkey_swap_info.rs diff --git a/node/src/rpc.rs b/node/src/rpc.rs index c169b8530..54f82447f 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -60,7 +60,6 @@ where C::Api: subtensor_custom_rpc_runtime_api::NeuronInfoRuntimeApi, C::Api: subtensor_custom_rpc_runtime_api::SubnetInfoRuntimeApi, C::Api: subtensor_custom_rpc_runtime_api::SubnetRegistrationRuntimeApi, - C::Api: subtensor_custom_rpc_runtime_api::ColdkeySwapRuntimeApi, B: sc_client_api::Backend + Send + Sync + 'static, P: TransactionPool + 'static, { diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 15157be2f..2f71e9c21 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -12,7 +12,7 @@ use std::sync::Arc; use sp_api::ProvideRuntimeApi; pub use subtensor_custom_rpc_runtime_api::{ - ColdkeySwapRuntimeApi, DelegateInfoRuntimeApi, NeuronInfoRuntimeApi, SubnetInfoRuntimeApi, + DelegateInfoRuntimeApi, NeuronInfoRuntimeApi, SubnetInfoRuntimeApi, SubnetRegistrationRuntimeApi, }; @@ -51,24 +51,6 @@ pub trait SubtensorCustomApi { #[method(name = "subnetInfo_getLockCost")] fn get_network_lock_cost(&self, at: Option) -> RpcResult; - #[method(name = "coldkeySwap_getScheduledColdkeySwap")] - fn get_scheduled_coldkey_swap( - &self, - coldkey_account_vec: Vec, - at: Option, - ) -> RpcResult>; - #[method(name = "coldkeySwap_getRemainingArbitrationPeriod")] - fn get_remaining_arbitration_period( - &self, - coldkey_account_vec: Vec, - at: Option, - ) -> RpcResult>; - #[method(name = "coldkeySwap_getColdkeySwapDestinations")] - fn get_coldkey_swap_destinations( - &self, - coldkey_account_vec: Vec, - at: Option, - ) -> RpcResult>; } pub struct SubtensorCustom { @@ -117,7 +99,6 @@ where C::Api: NeuronInfoRuntimeApi, C::Api: SubnetInfoRuntimeApi, C::Api: SubnetRegistrationRuntimeApi, - C::Api: ColdkeySwapRuntimeApi, { fn get_delegates(&self, at: Option<::Hash>) -> RpcResult> { let api = self.client.runtime_api(); @@ -242,51 +223,4 @@ where Error::RuntimeError(format!("Unable to get subnet lock cost: {:?}", e)).into() }) } - - fn get_scheduled_coldkey_swap( - &self, - coldkey_account_vec: Vec, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = at.unwrap_or_else(|| self.client.info().best_hash); - - api.get_scheduled_coldkey_swap(at, coldkey_account_vec) - .map_err(|e| { - Error::RuntimeError(format!("Unable to get scheduled coldkey swap: {:?}", e)).into() - }) - } - - fn get_remaining_arbitration_period( - &self, - coldkey_account_vec: Vec, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = at.unwrap_or_else(|| self.client.info().best_hash); - - api.get_remaining_arbitration_period(at, coldkey_account_vec) - .map_err(|e| { - Error::RuntimeError(format!( - "Unable to get remaining arbitration period: {:?}", - e - )) - .into() - }) - } - - fn get_coldkey_swap_destinations( - &self, - coldkey_account_vec: Vec, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = at.unwrap_or_else(|| self.client.info().best_hash); - - api.get_coldkey_swap_destinations(at, coldkey_account_vec) - .map_err(|e| { - Error::RuntimeError(format!("Unable to get coldkey swap destinations: {:?}", e)) - .into() - }) - } } diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index a647d3619..9095ad54a 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -32,10 +32,4 @@ sp_api::decl_runtime_apis! { pub trait SubnetRegistrationRuntimeApi { fn get_network_registration_cost() -> u64; } - - pub trait ColdkeySwapRuntimeApi { - fn get_scheduled_coldkey_swap( coldkey_account_vec: Vec ) -> Vec; - fn get_remaining_arbitration_period( coldkey_account_vec: Vec ) -> Vec; - fn get_coldkey_swap_destinations( coldkey_account_vec: Vec ) -> Vec; - } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 4326fba86..d4def0c27 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -51,7 +51,6 @@ mod weights; pub mod delegate_info; pub mod neuron_info; -pub mod schedule_coldkey_swap_info; pub mod stake_info; pub mod subnet_info; diff --git a/pallets/subtensor/src/schedule_coldkey_swap_info.rs b/pallets/subtensor/src/schedule_coldkey_swap_info.rs deleted file mode 100644 index e23ad23b6..000000000 --- a/pallets/subtensor/src/schedule_coldkey_swap_info.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::*; -use codec::Compact; -use frame_support::pallet_prelude::{Decode, Encode}; - -use sp_core::hexdisplay::AsBytesRef; - -#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] -pub struct ScheduledColdkeySwapInfo { - old_coldkey: T::AccountId, - new_coldkey: T::AccountId, - arbitration_block: Compact, -} - -impl Pallet { - /// Retrieves the scheduled coldkey swap information for an existing account. - /// - /// # Arguments - /// - /// * `coldkey` - The account ID of the coldkey to check. - /// - /// # Returns - /// - /// * `Option>` - The scheduled coldkey swap information if it exists, otherwise `None`. - /// - /// # Notes - /// - /// This function checks if there are any destination coldkeys associated with the given coldkey. - /// If there are, it retrieves the arbitration block and constructs the `ScheduledColdkeySwapInfo` struct. - fn get_scheduled_coldkey_swap_by_existing_account( - coldkey: AccountIdOf, - ) -> Option> { - let destinations: Vec = ColdkeySwapDestinations::::get(&coldkey); - if destinations.is_empty() { - return None; - } - - let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(&coldkey); - - Some(ScheduledColdkeySwapInfo { - old_coldkey: coldkey, - new_coldkey: destinations.first().cloned().unwrap_or_else(|| { - T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("Infinite length input; no invalid inputs for type; qed") - }), - arbitration_block: arbitration_block.into(), - }) - } - - /// Retrieves the scheduled coldkey swap information for a given coldkey account vector. - /// - /// # Arguments - /// - /// * `coldkey_account_vec` - The vector of bytes representing the coldkey account. - /// - /// # Returns - /// - /// * `Option>` - The scheduled coldkey swap information if it exists, otherwise `None`. - /// - /// # Notes - /// - /// This function decodes the coldkey account vector into an account ID and then calls - /// `get_scheduled_coldkey_swap_by_existing_account` to retrieve the swap information. - pub fn get_scheduled_coldkey_swap( - coldkey_account_vec: Vec, - ) -> Option> { - if coldkey_account_vec.len() != 32 { - return None; - } - - let coldkey: AccountIdOf = - T::AccountId::decode(&mut coldkey_account_vec.as_bytes_ref()).ok()?; - Self::get_scheduled_coldkey_swap_by_existing_account(coldkey) - } - - /// Retrieves all scheduled coldkey swaps from storage. - /// - /// # Returns - /// - /// * `Vec>` - A vector containing all scheduled coldkey swap information. - /// - /// # Notes - /// - /// This function iterates over all coldkeys in `ColdkeySwapDestinations` and retrieves their swap information - /// using `get_scheduled_coldkey_swap_by_existing_account`. - pub fn get_all_scheduled_coldkey_swaps() -> Vec> { - let mut scheduled_swaps: Vec> = Vec::new(); - for coldkey in ColdkeySwapDestinations::::iter_keys() { - if let Some(swap_info) = Self::get_scheduled_coldkey_swap_by_existing_account(coldkey) { - scheduled_swaps.push(swap_info); - } - } - scheduled_swaps - } - - /// Retrieves the scheduled coldkey swaps for a given block. - /// - /// # Arguments - /// - /// * `block` - The block number to check for scheduled coldkey swaps. - /// - /// # Returns - /// - /// * `Vec>` - A vector containing the scheduled coldkey swap information for the given block. - /// - /// # Notes - /// - /// This function retrieves the coldkeys to swap at the given block and then retrieves their swap information - /// using `get_scheduled_coldkey_swap_by_existing_account`. - pub fn get_scheduled_coldkey_swaps_at_block(block: u64) -> Vec> { - let coldkeys_to_swap: Vec = ColdkeysToSwapAtBlock::::get(block); - let mut scheduled_swaps: Vec> = Vec::new(); - for coldkey in coldkeys_to_swap { - if let Some(swap_info) = Self::get_scheduled_coldkey_swap_by_existing_account(coldkey) { - scheduled_swaps.push(swap_info); - } - } - scheduled_swaps - } - - /// Retrieves the remaining arbitration period for a given coldkey account vector. - /// - /// # Arguments - /// - /// * `coldkey_account_vec` - The vector of bytes representing the coldkey account. - /// - /// # Returns - /// - /// * `Option` - The remaining arbitration period in blocks if it exists, otherwise `None`. - /// - /// # Notes - /// - /// This function decodes the coldkey account vector into an account ID and calculates the remaining arbitration period - /// by subtracting the current block number from the arbitration block number. - pub fn get_remaining_arbitration_period(coldkey_account_vec: Vec) -> Option { - if coldkey_account_vec.len() != 32 { - return None; - } - - let coldkey: AccountIdOf = - T::AccountId::decode(&mut coldkey_account_vec.as_bytes_ref()).ok()?; - let current_block: u64 = Self::get_current_block_as_u64(); - let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(&coldkey); - - if arbitration_block > current_block { - Some(arbitration_block.saturating_sub(current_block)) - } else { - Some(0) - } - } - - /// Retrieves the destination coldkeys for a given coldkey account vector. - /// - /// # Arguments - /// - /// * `coldkey_account_vec` - The vector of bytes representing the coldkey account. - /// - /// # Returns - /// - /// * `Option>` - A vector containing the destination coldkeys if they exist, otherwise `None`. - /// - /// # Notes - /// - /// This function decodes the coldkey account vector into an account ID and retrieves the destination coldkeys - /// from `ColdkeySwapDestinations`. - pub fn get_coldkey_swap_destinations( - coldkey_account_vec: Vec, - ) -> Option> { - if coldkey_account_vec.len() != 32 { - return None; - } - - let coldkey: AccountIdOf = - T::AccountId::decode(&mut coldkey_account_vec.as_bytes_ref()).ok()?; - Some(ColdkeySwapDestinations::::get(&coldkey)) - } -} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3e5af1b43..de8be6e61 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1664,23 +1664,6 @@ impl_runtime_apis! { SubtensorModule::get_network_lock_cost() } } - - impl subtensor_custom_rpc_runtime_api::ColdkeySwapRuntimeApi for Runtime { - fn get_scheduled_coldkey_swap( coldkey_account_vec: Vec ) -> Vec { - let result = SubtensorModule::get_scheduled_coldkey_swap( coldkey_account_vec ); - result.encode() - } - - fn get_remaining_arbitration_period( coldkey_account_vec: Vec ) -> Vec { - let result = SubtensorModule::get_remaining_arbitration_period( coldkey_account_vec ); - result.encode() - } - - fn get_coldkey_swap_destinations( coldkey_account_vec: Vec ) -> Vec { - let result = SubtensorModule::get_coldkey_swap_destinations( coldkey_account_vec ); - result.encode() - } - } } // #[cfg(test)] From dc3f17815685a5a0f357685e0234b435bb338f2c Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 1 Jul 2024 17:01:28 +0200 Subject: [PATCH 135/185] Make weighted_median iterative --- pallets/subtensor/src/math.rs | 85 +++++++++++++++-------------------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/math.rs index 8b6e76f2c..dc067167f 100644 --- a/pallets/subtensor/src/math.rs +++ b/pallets/subtensor/src/math.rs @@ -888,59 +888,48 @@ pub fn weighted_median( score: &[I32F32], partition_idx: &[usize], minority: I32F32, - partition_lo: I32F32, - partition_hi: I32F32, + mut partition_lo: I32F32, + mut partition_hi: I32F32, ) -> I32F32 { - let n = partition_idx.len(); - if n == 0 { - return I32F32::from_num(0); - } - if n == 1 { - return score[partition_idx[0]]; - } - assert!(stake.len() == score.len()); - let mid_idx: usize = n.saturating_div(2); - let pivot: I32F32 = score[partition_idx[mid_idx]]; - let mut lo_stake: I32F32 = I32F32::from_num(0); - let mut hi_stake: I32F32 = I32F32::from_num(0); - let mut lower: Vec = vec![]; - let mut upper: Vec = vec![]; - for &idx in partition_idx { - if score[idx] == pivot { - continue; + let mut current_partition_idx = partition_idx.to_vec(); + while !current_partition_idx.is_empty() { + let n = current_partition_idx.len(); + if n == 1 { + return score[current_partition_idx[0]]; + } + let mid_idx: usize = n.saturating_div(2); + let pivot: I32F32 = score[current_partition_idx[mid_idx]]; + let mut lo_stake: I32F32 = I32F32::from_num(0); + let mut hi_stake: I32F32 = I32F32::from_num(0); + let mut lower: Vec = vec![]; + let mut upper: Vec = vec![]; + for &idx in ¤t_partition_idx { + if score[idx] == pivot { + continue; + } + if score[idx] < pivot { + lo_stake = lo_stake.saturating_add(stake[idx]); + lower.push(idx); + } else { + hi_stake = hi_stake.saturating_add(stake[idx]); + upper.push(idx); + } } - if score[idx] < pivot { - lo_stake = lo_stake.saturating_add(stake[idx]); - lower.push(idx); + if partition_lo.saturating_add(lo_stake) <= minority + && minority < partition_hi.saturating_sub(hi_stake) + { + return pivot; + } else if (minority < partition_lo.saturating_add(lo_stake)) && (!lower.is_empty()) { + current_partition_idx = lower; + partition_hi = partition_lo.saturating_add(lo_stake); + } else if (partition_hi.saturating_sub(hi_stake) <= minority) && (!upper.is_empty()) { + current_partition_idx = upper; + partition_lo = partition_hi.saturating_sub(hi_stake); } else { - hi_stake = hi_stake.saturating_add(stake[idx]); - upper.push(idx); + return pivot; } } - if (partition_lo.saturating_add(lo_stake) <= minority) - && (minority < partition_hi.saturating_sub(hi_stake)) - { - return pivot; - } else if (minority < partition_lo.saturating_add(lo_stake)) && (!lower.is_empty()) { - return weighted_median( - stake, - score, - &lower, - minority, - partition_lo, - partition_lo.saturating_add(lo_stake), - ); - } else if (partition_hi.saturating_sub(hi_stake) <= minority) && (!upper.is_empty()) { - return weighted_median( - stake, - score, - &upper, - minority, - partition_hi.saturating_sub(hi_stake), - partition_hi, - ); - } - pivot + I32F32::from_num(0) } /// Column-wise weighted median, e.g. stake-weighted median scores per server (column) over all validators (rows). From 177c47ac9ccfc356cd8901e814db903b8ba2988b Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 12 Jul 2024 12:29:15 -0400 Subject: [PATCH 136/185] fix merge conflict --- pallets/subtensor/tests/mock.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index ec7087eaf..3aa6123e1 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -1,7 +1,5 @@ #![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] -use frame_support::derive_impl; -use frame_support::dispatch::DispatchResultWithPostInfo; -use frame_support::weights::constants::RocksDbWeight; + // use frame_support::weights::constants::WEIGHT_PER_SECOND; use frame_support::weights::Weight; use frame_support::{ @@ -9,6 +7,7 @@ use frame_support::{ dispatch::DispatchResultWithPostInfo, parameter_types, traits::{Everything, Hooks}, + weights::constants::RocksDbWeight, }; use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; From 9d1f6c9cbd9b1bbf2d498b0a5025351ab87f0c8d Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 21:19:24 +0400 Subject: [PATCH 137/185] feat: remove schedule coldkey swap --- pallets/subtensor/src/benchmarks.rs | 26 ----------------------- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/swap.rs | 32 +++++++++++++++++------------ runtime/src/lib.rs | 3 +-- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 6da83c5af..80a375d10 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -429,30 +429,4 @@ reveal_weights { }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) - schedule_coldkey_swap { - let seed: u32 = 1; - let old_coldkey: T::AccountId = account("OldColdkey", 0, seed); - let new_coldkey: T::AccountId = account("NewColdkey", 0, seed + 1); - let hotkey: T::AccountId = account("Hotkey", 0, seed); - - let netuid = 1u16; - let tempo = 1u16; - let block_number: u64 = Subtensor::::get_current_block_as_u64(); - let nonce = 0; - - // Initialize the network - Subtensor::::init_new_network(netuid, tempo); - Subtensor::::set_network_registration_allowed(netuid, true); - - // Add balance to the old coldkey account - let amount_to_be_staked: u64 = 1000000u32.into(); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey.clone(), amount_to_be_staked+1000000000); - // Burned register the hotkey with the old coldkey - assert_ok!(Subtensor::::burned_register( - RawOrigin::Signed(old_coldkey.clone()).into(), - netuid, - hotkey.clone() - )); - - }: schedule_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone(), vec![], block_number, nonce) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d4def0c27..c4ac1f3e5 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2089,7 +2089,6 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { Self::do_swap_coldkey(origin, &new_coldkey) } - /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. /// /// # Arguments @@ -2105,6 +2104,7 @@ pub mod pallet { /// # Weight /// /// Weight is calculated based on the number of database reads and writes. + #[cfg(test)] #[pallet::call_index(72)] #[pallet::weight((Weight::from_parts(21_000_000, 0) .saturating_add(T::DbWeight::get().reads(3)) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 24d67aeb4..6c914f855 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -31,10 +31,6 @@ impl Pallet { new_hotkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); let mut weight = T::DbWeight::get().reads(2); @@ -60,6 +56,16 @@ impl Pallet { T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); + let swap_cost = Self::get_hotkey_swap_cost(); + log::debug!("Swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); @@ -189,15 +195,15 @@ impl Pallet { /// /// This function calculates the remaining arbitration period by subtracting the current block number /// from the arbitration block number of the coldkey. - // pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { - // let current_block: u64 = Self::get_current_block_as_u64(); - // let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); - // if arbitration_block > current_block { - // arbitration_block.saturating_sub(current_block) - // } else { - // 0 - // } - // } + pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { + let current_block: u64 = Self::get_current_block_as_u64(); + let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); + if arbitration_block > current_block { + arbitration_block.saturating_sub(current_block) + } else { + 0 + } + } pub fn meets_min_allowed_coldkey_balance(coldkey: &T::AccountId) -> bool { let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index de8be6e61..0e171e9ea 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -312,8 +312,7 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) | RuntimeCall::SubtensorModule( - pallet_subtensor::Call::schedule_coldkey_swap { .. } - | pallet_subtensor::Call::set_weights { .. } + pallet_subtensor::Call::set_weights { .. } | pallet_subtensor::Call::set_root_weights { .. } | pallet_subtensor::Call::serve_axon { .. } ) From 90512c28279dcb0b4a233b61351ece84284a1fdd Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 21:41:33 +0400 Subject: [PATCH 138/185] chore: fix tests --- pallets/subtensor/src/swap.rs | 15 ++++++++++++--- pallets/subtensor/tests/swap.rs | 34 ++++----------------------------- 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 2073aa5e2..02abda0d5 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -60,6 +60,16 @@ impl Pallet { T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); + let swap_cost = Self::get_hotkey_swap_cost(); + log::debug!("Swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); @@ -76,7 +86,6 @@ impl Pallet { Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); - Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; Self::set_last_tx_block(&coldkey, block); weight.saturating_accrue(T::DbWeight::get().writes(1)); @@ -950,15 +959,15 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } - /// Swaps the Senate membership from the old hotkey to the new hotkey if applicable. pub fn swap_senate_member( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight, ) -> DispatchResult { + weight.saturating_accrue(T::DbWeight::get().reads(1)); if T::SenateMembers::is_member(old_hotkey) { T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } Ok(()) } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 7445470f8..488f2e1d0 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -3,7 +3,7 @@ use codec::Encode; use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; -use frame_system::Config; +use frame_system::{Config, RawOrigin}; mod mock; use mock::*; use pallet_subtensor::*; @@ -1675,11 +1675,9 @@ fn test_swap_senate_member() { let mut weight = Weight::zero(); // Setup: Add old_hotkey as a Senate member - assert_ok!(Senate::set_members( - RuntimeOrigin::root(), - vec![old_hotkey], - None, - 0 + assert_ok!(SenateMembers::add_member( + RawOrigin::Root.into(), + old_hotkey )); // Test 1: Successful swap @@ -1710,29 +1708,5 @@ fn test_swap_senate_member() { // Verify weight update (should only have read operations) let expected_weight = ::DbWeight::get().reads(1); assert_eq!(weight, expected_weight); - - // Reset weight for next test - weight = Weight::zero(); - - // Test 3: Setup for swap to an existing member - let another_member = U256::from(4); - assert_ok!(Senate::set_members( - RuntimeOrigin::root(), - vec![new_hotkey, another_member], - None, - 1 - )); - - // Test 4: Setup for swap with maximum members reached - let mut members = vec![new_hotkey, another_member]; - for i in 5..=SenateMaxMembers::get() { - members.push(U256::from(i as u64)); - } - assert_ok!(Senate::set_members( - RuntimeOrigin::root(), - members.clone(), - None, - 2 - )); }); } From 606b771f7ebc1810bdb124c5170c9c5d2a726c89 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 22:02:12 +0400 Subject: [PATCH 139/185] fixes for network resumption --- pallets/admin-utils/tests/mock.rs | 4 ++-- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/swap.rs | 16 +++++++++++++++- pallets/subtensor/src/utils.rs | 4 ++-- pallets/subtensor/tests/mock.rs | 4 ++-- pallets/subtensor/tests/swap.rs | 8 +++++--- runtime/src/lib.rs | 4 ++-- 7 files changed, 29 insertions(+), 13 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 5f71626ad..dbf88bdfa 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -110,7 +110,7 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 1; - pub const InitialHotkeySwapCost: u64 = 1_000_000_000; + pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -166,7 +166,7 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; - type HotkeySwapCost = InitialHotkeySwapCost; + type KeySwapCost = InitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c4ac1f3e5..fc802a78a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -247,7 +247,7 @@ pub mod pallet { type InitialTargetStakesPerInterval: Get; /// Cost of swapping a hotkey. #[pallet::constant] - type HotkeySwapCost: Get; + type KeySwapCost: Get; /// The upper bound for the alpha parameter. Used for Liquid Alpha. #[pallet::constant] type AlphaHigh: Get; diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index c9e01b008..6e91f205b 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -56,7 +56,7 @@ impl Pallet { T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); - let swap_cost = Self::get_hotkey_swap_cost(); + let swap_cost = Self::get_key_swap_cost(); log::debug!("Swap cost: {:?}", swap_cost); ensure!( @@ -146,6 +146,20 @@ impl Pallet { Error::::ColdKeyAlreadyAssociated ); + // Calculate and charge the swap fee + let swap_cost = Self::get_key_swap_cost(); + log::debug!("Coldkey swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + let actual_burn_amount = + Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + // Actually do the swap. weight = weight.saturating_add( Self::perform_swap_coldkey(&old_coldkey, new_coldkey) diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 94f18dfbf..c61133e94 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -671,8 +671,8 @@ impl Pallet { NominatorMinRequiredStake::::put(min_stake); } - pub fn get_hotkey_swap_cost() -> u64 { - T::HotkeySwapCost::get() + pub fn get_key_swap_cost() -> u64 { + T::KeySwapCost::get() } pub fn get_alpha_values(netuid: u16) -> (u16, u16) { diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index c3a864075..46e08e96a 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -164,7 +164,7 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 2; - pub const InitialHotkeySwapCost: u64 = 1_000_000_000; + pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -369,7 +369,7 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; - type HotkeySwapCost = InitialHotkeySwapCost; + type KeySwapCost = InitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 488f2e1d0..4e2b80a47 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1059,7 +1059,8 @@ fn test_do_swap_coldkey_success() { let netuid = 1u16; let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; - let free_balance_old = 12345u64 + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; + let swap_cost = SubtensorModule::get_key_swap_cost(); + let free_balance_old = 12345u64 + swap_cost; // Setup initial state add_network(netuid, 13, 0); @@ -1158,7 +1159,7 @@ fn test_do_swap_coldkey_success() { // Verify balance transfer assert_eq!( SubtensorModule::get_coldkey_balance(&new_coldkey), - free_balance_old + free_balance_old - swap_cost ); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); @@ -1332,6 +1333,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { let hotkey = U256::from(3); let netuid = 1u16; let stake_amount: u64 = 1000u64; + let swap_cost = SubtensorModule::get_key_swap_cost(); // Setup initial state add_network(netuid, 13, 0); @@ -1340,7 +1342,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Set TotalNetworks because swap relies on it pallet_subtensor::TotalNetworks::::set(1); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); SubnetOwner::::insert(netuid, old_coldkey); // Populate OwnedHotkeys map diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0e171e9ea..45c7cd9da 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -876,7 +876,7 @@ parameter_types! { pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; pub const SubtensorInitialTargetStakesPerInterval: u16 = 1; - pub const SubtensorInitialHotkeySwapCost: u64 = 1_000_000_000; + pub const SubtensorInitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -932,7 +932,7 @@ impl pallet_subtensor::Config for Runtime { type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval; - type HotkeySwapCost = SubtensorInitialHotkeySwapCost; + type KeySwapCost = SubtensorInitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; From 8ebcf2dd7e1810b49500d70d9e2a0917e62533b0 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 22:08:59 +0400 Subject: [PATCH 140/185] chore: bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 45c7cd9da..b343ad0f0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 194, + spec_version: 162, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 7b84d3c95f3096659e9d92bcdc006db3d1a7b237 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 23:08:23 +0400 Subject: [PATCH 141/185] fix: add back arbitration check --- pallets/subtensor/src/swap.rs | 6 ++++++ pallets/subtensor/tests/swap.rs | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 6e91f205b..2e7be5122 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -32,6 +32,11 @@ impl Pallet { ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; + ensure!( + !Self::coldkey_in_arbitration(&old_coldkey), + Error::::ColdkeyIsInArbitration + ); + let mut weight = T::DbWeight::get().reads(2); ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); @@ -80,6 +85,7 @@ impl Pallet { Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 4e2b80a47..2355dc960 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -167,6 +167,22 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::add_balance_to_coldkey_account(coldkey, swap_cost); } + // Add old_hotkeys[0] and old_hotkeys[1] to Senate + assert_ok!(SenateMembers::add_member( + RawOrigin::Root.into(), + old_hotkeys[0] + )); + assert_ok!(SenateMembers::add_member( + RawOrigin::Root.into(), + old_hotkeys[1] + )); + + // Verify initial Senate membership + assert!(Senate::is_member(&old_hotkeys[0])); + assert!(Senate::is_member(&old_hotkeys[1])); + assert!(!Senate::is_member(&new_hotkeys[0])); + assert!(!Senate::is_member(&new_hotkeys[1])); + // Perform the swaps for only two hotkeys assert_ok!(SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(coldkeys[0]), @@ -268,6 +284,10 @@ fn test_do_swap_hotkey_ok_robust() { assert_eq!(Keys::::get(netuid, uid), new_hotkeys[i]); } } + + // Verify Senate membership swap + assert!(!Senate::is_member(&old_hotkeys[i])); + assert!(Senate::is_member(&new_hotkeys[i])); } else { // Ensure other hotkeys remain unchanged assert_eq!( @@ -278,6 +298,10 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkeys[i]), coldkeys[i] ); + + // Verify Senate membership remains unchanged for other hotkeys + assert!(!Senate::is_member(&old_hotkeys[i])); + assert!(!Senate::is_member(&new_hotkeys[i])); } } } From 2455082e007069e6cd3ced93a2bee10774cebc44 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 23:19:53 +0400 Subject: [PATCH 142/185] fix: hotkey --- pallets/subtensor/src/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 2e7be5122..ff93a1dbc 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -33,7 +33,7 @@ impl Pallet { let coldkey = ensure_signed(origin)?; ensure!( - !Self::coldkey_in_arbitration(&old_coldkey), + !Self::coldkey_in_arbitration(&old_hotkey), Error::::ColdkeyIsInArbitration ); From f49bb55de0605ee47f9a1883f098ca00afe62926 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 12 Jul 2024 23:24:21 +0400 Subject: [PATCH 143/185] fix: coldkey arb swap hotkey --- pallets/subtensor/src/swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index ff93a1dbc..307aeb641 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -33,7 +33,7 @@ impl Pallet { let coldkey = ensure_signed(origin)?; ensure!( - !Self::coldkey_in_arbitration(&old_hotkey), + !Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration ); From 76f20cedc4d9314f28e3437b0b5a6f38d9cd7505 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 12 Jul 2024 23:38:31 -0400 Subject: [PATCH 144/185] swap delegate stake also --- pallets/subtensor/src/swap.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 307aeb641..053112363 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -862,6 +862,22 @@ impl Pallet { } } + for staking_hotkey in StakingHotkeys::::get(old_coldkey) { + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::take(hotkey, old_coldkey); + log::info!("Transferring delegated stake for hotkey {:?}: {}", hotkey, stake); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + Stake::::insert(hotkey, new_coldkey, stake); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + } + } + + // Log the total transferred stake log::info!("Total transferred stake: {}", total_transferred_stake); From 4ccb8799eb0454e9581322a56c3e391cd67a9f12 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 12 Jul 2024 23:38:37 -0400 Subject: [PATCH 145/185] add test for this --- pallets/subtensor/tests/swap.rs | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 2355dc960..ca45956d6 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1271,6 +1271,67 @@ fn test_swap_stake_for_coldkey() { }); } +#[test] +fn test_swap_delegated_stake_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let total_stake = stake_amount1 + stake_amount2; + let mut weight = Weight::zero(); + + // Notice hotkey1 and hotkey2 are not in OwnedHotkeys + // coldkey therefore delegates stake to them + + // Setup initial state + Stake::::insert(hotkey1, old_coldkey, stake_amount1); + Stake::::insert(hotkey2, old_coldkey, stake_amount2); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); + TotalHotkeyStake::::insert(hotkey2, stake_amount2); + TotalColdkeyStake::::insert(old_coldkey, total_stake); + + // Set up total issuance + TotalIssuance::::put(total_stake); + TotalStake::::put(total_stake); + + // Record initial values + let initial_total_issuance = SubtensorModule::get_total_issuance(); + let initial_total_stake = SubtensorModule::get_total_stake(); + + // Perform the swap + SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify stake transfer + assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); + assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); + assert_eq!(Stake::::get(hotkey1, old_coldkey), 0); + assert_eq!(Stake::::get(hotkey2, old_coldkey), 0); + + // Verify TotalColdkeyStake + assert_eq!(TotalColdkeyStake::::get(new_coldkey), total_stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + + // Verify TotalHotkeyStake remains unchanged + assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); + assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); + + // Verify total stake and issuance remain unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + initial_total_stake, + "Total stake changed unexpectedly" + ); + assert_eq!( + SubtensorModule::get_total_issuance(), + initial_total_issuance, + "Total issuance changed unexpectedly" + ); + }); +} + #[test] fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { new_test_ext(1).execute_with(|| { From 73533b9bf89899b005a44e1f5932e37110efb229 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 00:53:34 -0400 Subject: [PATCH 146/185] swap over stakinghotkeys map --- pallets/subtensor/src/swap.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 053112363..581d61799 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -909,7 +909,8 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Update the staking hotkeys for both old and new coldkeys - let staking_hotkeys: Vec = StakingHotkeys::::take(old_coldkey); + let staking_hotkeys: Vec = StakingHotkeys::::take(old_coldkey); + StakingHotkeys::::remove(old_coldkey); StakingHotkeys::::insert(new_coldkey, staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); From c370aad5e3f0eda1250740b97fc498c216be049b Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 00:55:11 -0400 Subject: [PATCH 147/185] check map first --- pallets/subtensor/src/swap.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 581d61799..c6d3a5a8a 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -863,18 +863,23 @@ impl Pallet { } for staking_hotkey in StakingHotkeys::::get(old_coldkey) { - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::take(hotkey, old_coldkey); - log::info!("Transferring delegated stake for hotkey {:?}: {}", hotkey, stake); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - Stake::::insert(hotkey, new_coldkey, stake); - total_transferred_stake = total_transferred_stake.saturating_add(stake); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); - } + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::take(hotkey, old_coldkey); + log::info!("Transferring delegated stake for hotkey {:?}: {}", hotkey, stake); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + Stake::::insert(hotkey, new_coldkey, stake); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + } + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } } From 9b8c0e4944b971d39519d59340ad132e597c2cf6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 00:57:37 -0400 Subject: [PATCH 148/185] add staking hotkeys test --- pallets/subtensor/tests/swap.rs | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index ca45956d6..de5c4212e 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1271,6 +1271,40 @@ fn test_swap_stake_for_coldkey() { }); } +#[test] +fn test_swap_staking_hotkeys_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let total_stake = stake_amount1 + stake_amount2; + let mut weight = Weight::zero(); + + // Setup initial state + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + Stake::::insert(hotkey1, old_coldkey, stake_amount1); + Stake::::insert(hotkey2, old_coldkey, stake_amount2); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); + TotalHotkeyStake::::insert(hotkey2, stake_amount2); + TotalColdkeyStake::::insert(old_coldkey, total_stake); + + // Set up total issuance + TotalIssuance::::put(total_stake); + TotalStake::::put(total_stake); + + // Perform the swap + SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + + // Verify StakingHotkeys transfer + assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey1, hotkey2]); + assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); + }); +} + #[test] fn test_swap_delegated_stake_for_coldkey() { new_test_ext(1).execute_with(|| { From baec581eceeb646bd6aefc8bacfc9e3408c31536 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 00:59:42 -0400 Subject: [PATCH 149/185] no take --- pallets/subtensor/src/swap.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index c6d3a5a8a..5fa92d531 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -867,7 +867,8 @@ impl Pallet { let hotkey = &staking_hotkey; // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::take(hotkey, old_coldkey); + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); log::info!("Transferring delegated stake for hotkey {:?}: {}", hotkey, stake); if stake > 0 { // Insert the stake for the hotkey and new coldkey From a84ac6b478ff23148c6ea45170e54458a3596858 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 01:01:38 -0400 Subject: [PATCH 150/185] keep old stake --- pallets/subtensor/src/swap.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 5fa92d531..3a557e178 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -872,7 +872,8 @@ impl Pallet { log::info!("Transferring delegated stake for hotkey {:?}: {}", hotkey, stake); if stake > 0 { // Insert the stake for the hotkey and new coldkey - Stake::::insert(hotkey, new_coldkey, stake); + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); total_transferred_stake = total_transferred_stake.saturating_add(stake); // Update the transaction weight @@ -915,7 +916,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Update the staking hotkeys for both old and new coldkeys - let staking_hotkeys: Vec = StakingHotkeys::::take(old_coldkey); + let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); StakingHotkeys::::remove(old_coldkey); StakingHotkeys::::insert(new_coldkey, staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); From 221d3204235665d0781e3aca52a168cd21814541 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 01:03:12 -0400 Subject: [PATCH 151/185] add check to test --- pallets/subtensor/tests/swap.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index de5c4212e..60b5b10c1 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1214,6 +1214,7 @@ fn test_swap_stake_for_coldkey() { let hotkey2 = U256::from(4); let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; + let stake_amount3 = 3000u64; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); @@ -1221,6 +1222,10 @@ fn test_swap_stake_for_coldkey() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); + + // Insert existing for same hotkey1 + Stake::::insert(hotkey1, new_coldkey, stake_amount3); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); TotalHotkeyStake::::insert(hotkey2, stake_amount2); TotalColdkeyStake::::insert(old_coldkey, total_stake); @@ -1249,6 +1254,9 @@ fn test_swap_stake_for_coldkey() { assert_eq!(Stake::::get(hotkey1, old_coldkey), 0); assert_eq!(Stake::::get(hotkey2, old_coldkey), 0); + // Verify stake is additive, not replaced + assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1 + stake_amount3); + // Verify TotalColdkeyStake assert_eq!(TotalColdkeyStake::::get(new_coldkey), total_stake); assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); From f9164fc1a023d497779163e58755edf39fed2ebb Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 01:27:56 -0400 Subject: [PATCH 152/185] fix some tests --- pallets/subtensor/tests/swap.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 60b5b10c1..637a0b4a9 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1220,11 +1220,13 @@ fn test_swap_stake_for_coldkey() { // Setup initial state OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); // Insert existing for same hotkey1 Stake::::insert(hotkey1, new_coldkey, stake_amount3); + StakingHotkeys::::insert(new_coldkey, vec![hotkey1]); TotalHotkeyStake::::insert(hotkey1, stake_amount1); TotalHotkeyStake::::insert(hotkey2, stake_amount2); @@ -1295,6 +1297,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); TotalHotkeyStake::::insert(hotkey1, stake_amount1); TotalHotkeyStake::::insert(hotkey2, stake_amount2); TotalColdkeyStake::::insert(old_coldkey, total_stake); @@ -1329,6 +1332,7 @@ fn test_swap_delegated_stake_for_coldkey() { // coldkey therefore delegates stake to them // Setup initial state + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); TotalHotkeyStake::::insert(hotkey1, stake_amount1); From 2a14df766e19655915f4c79b8e6cccbd81a947b7 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 01:28:07 -0400 Subject: [PATCH 153/185] fix delegate test --- pallets/subtensor/tests/swap.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 637a0b4a9..0aecdbbac 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1251,7 +1251,6 @@ fn test_swap_stake_for_coldkey() { assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); // Verify stake transfer - assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); assert_eq!(Stake::::get(hotkey1, old_coldkey), 0); assert_eq!(Stake::::get(hotkey2, old_coldkey), 0); From b970f50be23a731542494f5212011bcee9dcbf77 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 01:28:18 -0400 Subject: [PATCH 154/185] update staking hotekys maps --- pallets/subtensor/src/swap.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 3a557e178..3cddff97e 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -917,8 +917,11 @@ impl Pallet { // Update the staking hotkeys for both old and new coldkeys let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); + let old_staking_hotkeys: Vec = StakingHotkeys::::get(new_coldkey); + + let combined_staking_hotkeys: Vec = staking_hotkeys.into_iter().chain(old_staking_hotkeys.into_iter()).collect(); StakingHotkeys::::remove(old_coldkey); - StakingHotkeys::::insert(new_coldkey, staking_hotkeys); + StakingHotkeys::::insert(new_coldkey, combined_staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); // Log the total stake of old and new coldkeys after the swap From ad57e2bb50e7a55d5edbd9b84a7cba8fb4a98a98 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 01:52:02 -0400 Subject: [PATCH 155/185] init --- pallets/subtensor/src/migration.rs | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 35b3c4260..72caa0743 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -603,3 +603,55 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { Weight::zero() } } + +const LOG_TARGET_6: &str = "swapdelegate"; + +const ALREADY_SWAPPED_COLDKEYS = { + ("coldkey", "coldkey"), +}; + +pub fn migrate_to_v6_swap_delegate_stake_also() -> Weight { + let new_storage_version = 6; + + // Check storage version + let mut weight = T::DbWeight::get().reads(1); + + // Grab current version + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only runs if we haven't already updated version past above new_storage_version. + if onchain_version < new_storage_version { + info!(target: LOG_TARGET_6, ">>> Swapping delegate stake for an already-swapped coldkey {:?}", onchain_version); + + for (old_coldkey, _) in ColdkeyArbitrationBlock::::iter() { + // Get new coldkey + let new_coldkey = ALREADY_SWAPPED_COLDKEYS.find(|x| x.0 == old_coldkey); + // Grab hotkeys + let staking_hotkeys = StakingHotkeys::::get(&old_coldkey); + // Iterate over hotkeys + for hotkey in staking_hotkeys { + // Get the stake + let stake = Stake::::get(&hotkey, &old_coldkey); + Stake::::remove(&hotkey, &old_coldkey); + + let old_stake = Stake::::get(&hotkey, &new_coldkey); + // Add the stake to the new coldkey + Stake::::insert(&hotkey, &new_coldkey, stake.saturating_add(old_stake)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + StakingHotkeys::::remove(&old_coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + // Update storage version. + StorageVersion::new(new_storage_version).put::>(); // Update to version so we don't run this again. + // One write to storage version + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight + } else { + info!(target: LOG_TARGET_6, "Migration to v6 already done!"); + Weight::zero() + } +} From 1cc7d3cb8ca23d40295d1da2ebc53f7bd9185e88 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 02:05:39 -0400 Subject: [PATCH 156/185] comment out --- pallets/subtensor/src/lib.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index aa7cafde0..de551096b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2061,17 +2061,17 @@ pub mod pallet { } /// The extrinsic for user to change its hotkey - #[pallet::call_index(70)] - #[pallet::weight((Weight::from_parts(1_940_000_000, 0) - .saturating_add(T::DbWeight::get().reads(272)) - .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - pub fn swap_hotkey( - origin: OriginFor, - hotkey: T::AccountId, - new_hotkey: T::AccountId, - ) -> DispatchResultWithPostInfo { - Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) - } + ///#[pallet::call_index(70)] + ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) + ///.saturating_add(T::DbWeight::get().reads(272)) + ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + ///pub fn swap_hotkey( + /// origin: OriginFor, + /// hotkey: T::AccountId, + /// new_hotkey: T::AccountId, + ///) -> DispatchResultWithPostInfo { + /// Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) + ///} /// The extrinsic for user to change the coldkey associated with their account. /// From 6dcd0a13eecbfca7a45961966408632902099f16 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 02:13:49 -0400 Subject: [PATCH 157/185] add admin swap --- pallets/admin-utils/src/lib.rs | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 9a8744dc6..48b003a4b 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -17,6 +17,7 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_support::traits::tokens::Balance; use frame_system::pallet_prelude::*; + use pallet_subtensor::StakingHotkeys; use sp_runtime::BoundedVec; /// The main data structure of the module. @@ -1034,6 +1035,43 @@ pub mod pallet { ) -> DispatchResult { T::Subtensor::ensure_subnet_owner_or_root(origin.clone(), netuid)?; T::Subtensor::do_set_alpha_values(origin, netuid, alpha_low, alpha_high) + } + + /// Sets values for liquid alpha + #[pallet::call_index(52)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_hotfix_swap_coldkey_delegates( + origin: OriginFor, + old_coldkey: T::AccountId, + new_coldkey: T::AccountId, + ) -> DispatchResult { + T::Subtensor::ensure_root(origin.clone())?; + + let weight = T::DbWeight::get().reads_writes(2, 1); + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + for staking_hotkey in staking_hotkeys { + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + } + } + } + + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + for hotkey in staking_hotkeys { + if !existing_staking_hotkeys.contains(&hotkey) { + existing_staking_hotkeys.push(hotkey); + } + } + StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); + StakingHotkeys::::remove(old_coldkey); } } } From f474112da9ea61e5cb605ca4889f1d14db9cb4a7 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 02:13:54 -0400 Subject: [PATCH 158/185] fix swap --- pallets/subtensor/src/swap.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 3cddff97e..1a636f676 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -911,17 +911,30 @@ impl Pallet { } // Update the list of owned hotkeys for both old and new coldkeys - OwnedHotkeys::::remove(old_coldkey); - OwnedHotkeys::::insert(new_coldkey, old_owned_hotkeys); + + let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + for hotkey in old_owned_hotkeys { + if !new_owned_hotkeys.contains(&hotkey) { + new_owned_hotkeys.push(hotkey); + } + } + + OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); + OwnedHotkeys::::remove(old_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Update the staking hotkeys for both old and new coldkeys let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); - let old_staking_hotkeys: Vec = StakingHotkeys::::get(new_coldkey); - let combined_staking_hotkeys: Vec = staking_hotkeys.into_iter().chain(old_staking_hotkeys.into_iter()).collect(); + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + for hotkey in staking_hotkeys { + if !existing_staking_hotkeys.contains(&hotkey) { + existing_staking_hotkeys.push(hotkey); + } + } + StakingHotkeys::::remove(old_coldkey); - StakingHotkeys::::insert(new_coldkey, combined_staking_hotkeys); + StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); // Log the total stake of old and new coldkeys after the swap From b708c1d38b070144bc351122642dcb74991a2deb Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 13 Jul 2024 02:23:31 -0400 Subject: [PATCH 159/185] .. --- pallets/admin-utils/src/lib.rs | 30 ++-------- pallets/admin-utils/tests/tests.rs | 18 ++++++ pallets/subtensor/src/migration.rs | 96 +++++++++++++++--------------- pallets/subtensor/src/swap.rs | 33 ++++++++++ pallets/subtensor/tests/swap.rs | 79 ++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 75 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 48b003a4b..a471a8387 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1047,32 +1047,10 @@ pub mod pallet { ) -> DispatchResult { T::Subtensor::ensure_root(origin.clone())?; - let weight = T::DbWeight::get().reads_writes(2, 1); - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - for staking_hotkey in staking_hotkeys { - if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - } - } - } - - let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); - for hotkey in staking_hotkeys { - if !existing_staking_hotkeys.contains(&hotkey) { - existing_staking_hotkeys.push(hotkey); - } - } - StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); - StakingHotkeys::::remove(old_coldkey); - } + T::Subtensor::swap_hotfix(old_coldkey, new_coldkey); + + Ok(()) + } } } diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 9df59978f..95ea84643 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1361,3 +1361,21 @@ fn test_sudo_get_set_alpha() { )); }); } + + +#[test] +fn test_sudo_hotfix_swap_coldkey_delegates() { + new_test_ext().execute_with(|| { + let new_coldkey = U256::from(0); + let owner = U256::from(1); + let coldkey = U256::from(4); + + assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( + <::RuntimeOrigin>::root(), + coldkey, + new_coldkey + )); + + + }); +} diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 72caa0743..277c8e531 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -606,52 +606,50 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { const LOG_TARGET_6: &str = "swapdelegate"; -const ALREADY_SWAPPED_COLDKEYS = { - ("coldkey", "coldkey"), -}; - -pub fn migrate_to_v6_swap_delegate_stake_also() -> Weight { - let new_storage_version = 6; - - // Check storage version - let mut weight = T::DbWeight::get().reads(1); - - // Grab current version - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only runs if we haven't already updated version past above new_storage_version. - if onchain_version < new_storage_version { - info!(target: LOG_TARGET_6, ">>> Swapping delegate stake for an already-swapped coldkey {:?}", onchain_version); - - for (old_coldkey, _) in ColdkeyArbitrationBlock::::iter() { - // Get new coldkey - let new_coldkey = ALREADY_SWAPPED_COLDKEYS.find(|x| x.0 == old_coldkey); - // Grab hotkeys - let staking_hotkeys = StakingHotkeys::::get(&old_coldkey); - // Iterate over hotkeys - for hotkey in staking_hotkeys { - // Get the stake - let stake = Stake::::get(&hotkey, &old_coldkey); - Stake::::remove(&hotkey, &old_coldkey); - - let old_stake = Stake::::get(&hotkey, &new_coldkey); - // Add the stake to the new coldkey - Stake::::insert(&hotkey, &new_coldkey, stake.saturating_add(old_stake)); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - - StakingHotkeys::::remove(&old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - } - - // Update storage version. - StorageVersion::new(new_storage_version).put::>(); // Update to version so we don't run this again. - // One write to storage version - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight - } else { - info!(target: LOG_TARGET_6, "Migration to v6 already done!"); - Weight::zero() - } -} +// const ALREADY_SWAPPED_COLDKEYS = []; + +// pub fn migrate_to_v6_swap_delegate_stake_also() -> Weight { +// let new_storage_version = 6; + +// // Check storage version +// let mut weight = T::DbWeight::get().reads(1); + +// // Grab current version +// let onchain_version = Pallet::::on_chain_storage_version(); + +// // Only runs if we haven't already updated version past above new_storage_version. +// if onchain_version < new_storage_version { +// info!(target: LOG_TARGET_6, ">>> Swapping delegate stake for an already-swapped coldkey {:?}", onchain_version); + +// for (old_coldkey, _) in ColdkeyArbitrationBlock::::iter() { +// // Get new coldkey +// let new_coldkey = ALREADY_SWAPPED_COLDKEYS.find(|x| x.0 == old_coldkey); +// // Grab hotkeys +// let staking_hotkeys = StakingHotkeys::::get(&old_coldkey); +// // Iterate over hotkeys +// for hotkey in staking_hotkeys { +// // Get the stake +// let stake = Stake::::get(&hotkey, &old_coldkey); +// Stake::::remove(&hotkey, &old_coldkey); + +// let old_stake = Stake::::get(&hotkey, &new_coldkey); +// // Add the stake to the new coldkey +// Stake::::insert(&hotkey, &new_coldkey, stake.saturating_add(old_stake)); +// weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); +// } + +// StakingHotkeys::::remove(&old_coldkey); +// weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); +// } + +// // Update storage version. +// StorageVersion::new(new_storage_version).put::>(); // Update to version so we don't run this again. +// // One write to storage version +// weight.saturating_accrue(T::DbWeight::get().writes(1)); + +// weight +// } else { +// info!(target: LOG_TARGET_6, "Migration to v6 already done!"); +// Weight::zero() +// } +// } diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 1a636f676..d53175f92 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -947,6 +947,39 @@ impl Pallet { TotalColdkeyStake::::get(new_coldkey) ); } + + + pub fn swap_hotfix(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { + + let weight = T::DbWeight::get().reads_writes(2, 1); + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + for staking_hotkey in staking_hotkeys { + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + } + } + } + + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + for hotkey in staking_hotkeys { + if !existing_staking_hotkeys.contains(&hotkey) { + existing_staking_hotkeys.push(hotkey); + } + } + StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); + StakingHotkeys::::remove(old_coldkey); + } + /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. /// /// # Arguments diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 0aecdbbac..97347fe4d 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1842,3 +1842,82 @@ fn test_swap_senate_member() { assert_eq!(weight, expected_weight); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_delegations --exact --nocapture +#[test] +fn test_coldkey_delegations() { + new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(0); + let owner = U256::from(1); + let coldkey = U256::from(4); + let delegate = U256::from(2); + let netuid = 1u16; + add_network(netuid, 13, 0); + register_ok_neuron(netuid, delegate, owner, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(owner), + delegate, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + delegate, + 100 + )); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey + )); + assert_eq!( SubtensorModule::get_total_stake_for_hotkey( &delegate), 100 ); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey), 0 ); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &new_coldkey), 100 ); + assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); + assert_eq!( Stake::::get( delegate, coldkey ), 0 ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates --exact --nocapture +#[test] +fn test_sudo_hotfix_swap_coldkey_delegates() { + new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(0); + let owner = U256::from(1); + let coldkey = U256::from(4); + let delegate = U256::from(2); + let netuid = 1u16; + add_network(netuid, 13, 0); + register_ok_neuron(netuid, delegate, owner, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(owner), + delegate, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + delegate, + 100 + )); + + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey + )); + + + assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( + <::RuntimeOrigin>::root(), + to_be_set + )); + + assert_eq!( SubtensorModule::get_total_stake_for_hotkey( &delegate), 100 ); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey), 0 ); + assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &new_coldkey), 100 ); + assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); + assert_eq!( Stake::::get( delegate, coldkey ), 0 ); + + + }); +} + From 5b30256fae2fe3a80146a76c80af839d092fb554 Mon Sep 17 00:00:00 2001 From: const Date: Sat, 13 Jul 2024 02:04:04 -0500 Subject: [PATCH 160/185] hotkey staking maps fix --- pallets/admin-utils/src/lib.rs | 15 -- pallets/admin-utils/tests/tests.rs | 18 -- pallets/subtensor/src/lib.rs | 14 ++ pallets/subtensor/src/swap.rs | 9 +- pallets/subtensor/tests/registration.rs | 284 ++++++++++++------------ pallets/subtensor/tests/swap.rs | 119 +++++++--- runtime/src/lib.rs | 2 +- 7 files changed, 246 insertions(+), 215 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index a471a8387..71b709a10 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1036,21 +1036,6 @@ pub mod pallet { T::Subtensor::ensure_subnet_owner_or_root(origin.clone(), netuid)?; T::Subtensor::do_set_alpha_values(origin, netuid, alpha_low, alpha_high) } - - /// Sets values for liquid alpha - #[pallet::call_index(52)] - #[pallet::weight((0, DispatchClass::Operational, Pays::No))] - pub fn sudo_hotfix_swap_coldkey_delegates( - origin: OriginFor, - old_coldkey: T::AccountId, - new_coldkey: T::AccountId, - ) -> DispatchResult { - T::Subtensor::ensure_root(origin.clone())?; - - T::Subtensor::swap_hotfix(old_coldkey, new_coldkey); - - Ok(()) - } } } diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 95ea84643..9df59978f 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1361,21 +1361,3 @@ fn test_sudo_get_set_alpha() { )); }); } - - -#[test] -fn test_sudo_hotfix_swap_coldkey_delegates() { - new_test_ext().execute_with(|| { - let new_coldkey = U256::from(0); - let owner = U256::from(1); - let coldkey = U256::from(4); - - assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( - <::RuntimeOrigin>::root(), - coldkey, - new_coldkey - )); - - - }); -} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index de551096b..60d6c06d1 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -6,6 +6,7 @@ // pub use pallet::*; +use crate::system::ensure_root; use frame_system::{self as system, ensure_signed}; use frame_support::{ @@ -2253,6 +2254,19 @@ pub mod pallet { pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { Self::user_remove_network(origin, netuid) } + + /// Sets values for liquid alpha + #[pallet::call_index(64)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_hotfix_swap_coldkey_delegates( + origin: OriginFor, + old_coldkey: T::AccountId, + new_coldkey: T::AccountId, + ) -> DispatchResult { + ensure_root(origin)?; + Self::swap_hotfix(&old_coldkey, &new_coldkey); + Ok(()) + } } // ---- Subtensor helper functions. diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index d53175f92..b8b0cc459 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -851,7 +851,8 @@ impl Pallet { log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); if stake > 0 { // Insert the stake for the hotkey and new coldkey - Stake::::insert(hotkey, new_coldkey, stake); + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); total_transferred_stake = total_transferred_stake.saturating_add(stake); // Update the owner of the hotkey to the new coldkey @@ -861,10 +862,11 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } } + log::info!("Starting transfer of delegated stakes for old coldkey: {:?}", old_coldkey); for staking_hotkey in StakingHotkeys::::get(old_coldkey) { + log::info!("Processing staking hotkey: {:?}", staking_hotkey); if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - let hotkey = &staking_hotkey; // Retrieve and remove the stake associated with the hotkey and old coldkey let stake: u64 = Stake::::get(hotkey, old_coldkey); @@ -875,15 +877,18 @@ impl Pallet { let old_stake = Stake::::get(hotkey, new_coldkey); Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); total_transferred_stake = total_transferred_stake.saturating_add(stake); + log::info!("Updated stake for hotkey {:?} under new coldkey {:?}: {}", hotkey, new_coldkey, stake.saturating_add(old_stake)); // Update the transaction weight weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); } } else { + log::info!("No stake found for staking hotkey {:?} under old coldkey {:?}", staking_hotkey, old_coldkey); weight.saturating_accrue(T::DbWeight::get().reads(1)); } } + log::info!("Completed transfer of delegated stakes for old coldkey: {:?}", old_coldkey); // Log the total transferred stake log::info!("Total transferred stake: {}", total_transferred_stake); diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 676d49a44..bd95ae3b1 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -1868,153 +1868,153 @@ fn test_registration_disabled() { }); } -#[ignore] -#[test] -fn test_hotkey_swap_ok() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(667); - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10_000_000_000); - - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); - - let new_hotkey = U256::from(1337); - assert_ok!(SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - new_hotkey - )); - assert_ne!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey_account_id), - coldkey_account_id - ); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), - coldkey_account_id - ); - }); -} - -#[ignore] -#[test] -fn test_hotkey_swap_not_owner() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(2); - let not_owner_coldkey = U256::from(3); - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); - - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); - - let new_hotkey = U256::from(4); - assert_err!( - SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(not_owner_coldkey), - hotkey_account_id, - new_hotkey - ), - Error::::NonAssociatedColdKey - ); - }); -} - -#[ignore] -#[test] -fn test_hotkey_swap_same_key() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(2); - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); +// #[ignore] +// #[test] +// fn test_hotkey_swap_ok() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(667); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10_000_000_000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); +// let new_hotkey = U256::from(1337); +// assert_ok!(SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// hotkey_account_id, +// new_hotkey +// )); +// assert_ne!( +// SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey_account_id), +// coldkey_account_id +// ); +// assert_eq!( +// SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), +// coldkey_account_id +// ); +// }); +// } - assert_err!( - SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - hotkey_account_id - ), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - }); -} +// #[ignore] +// #[test] +// fn test_hotkey_swap_not_owner() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(2); +// let not_owner_coldkey = U256::from(3); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); -#[ignore] -#[test] -fn test_hotkey_swap_registered_key() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(2); +// let new_hotkey = U256::from(4); +// assert_err!( +// SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(not_owner_coldkey), +// hotkey_account_id, +// new_hotkey +// ), +// Error::::NonAssociatedColdKey +// ); +// }); +// } - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); +// #[ignore] +// #[test] +// fn test_hotkey_swap_same_key() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(2); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 100_000_000_000); +// assert_err!( +// SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// hotkey_account_id, +// hotkey_account_id +// ), +// Error::::HotKeyAlreadyRegisteredInSubNet +// ); +// }); +// } - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); +// #[ignore] +// #[test] +// fn test_hotkey_swap_registered_key() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(2); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 100_000_000_000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); - let new_hotkey = U256::from(3); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - new_hotkey - )); +// let new_hotkey = U256::from(3); +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// new_hotkey +// )); - assert_err!( - SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - new_hotkey - ), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - }); -} +// assert_err!( +// SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// hotkey_account_id, +// new_hotkey +// ), +// Error::::HotKeyAlreadyRegisteredInSubNet +// ); +// }); +// } diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 97347fe4d..2f075f729 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1205,6 +1205,7 @@ fn test_do_swap_coldkey_success() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_swap_stake_for_coldkey --exact --nocaptur #[test] fn test_swap_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1223,6 +1224,8 @@ fn test_swap_stake_for_coldkey() { StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1 ); + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1 ); // Insert existing for same hotkey1 Stake::::insert(hotkey1, new_coldkey, stake_amount3); @@ -1243,6 +1246,9 @@ fn test_swap_stake_for_coldkey() { // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + // Verify stake is additive, not replaced + assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1 + stake_amount3); + // Verify ownership transfer assert_eq!( SubtensorModule::get_owned_hotkeys(&new_coldkey), @@ -1255,9 +1261,6 @@ fn test_swap_stake_for_coldkey() { assert_eq!(Stake::::get(hotkey1, old_coldkey), 0); assert_eq!(Stake::::get(hotkey2, old_coldkey), 0); - // Verify stake is additive, not replaced - assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1 + stake_amount3); - // Verify TotalColdkeyStake assert_eq!(TotalColdkeyStake::::get(new_coldkey), total_stake); assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); @@ -1877,47 +1880,89 @@ fn test_coldkey_delegations() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates --exact --nocapture +// #[test] +// fn test_sudo_hotfix_swap_coldkey_delegates() { +// new_test_ext(1).execute_with(|| { +// let new_coldkey = U256::from(0); +// let owner = U256::from(1); +// let coldkey = U256::from(4); +// let delegate = U256::from(2); +// let netuid = 1u16; +// add_network(netuid, 13, 0); +// register_ok_neuron(netuid, delegate, owner, 0); +// SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); +// assert_ok!(SubtensorModule::do_become_delegate( +// <::RuntimeOrigin>::signed(owner), +// delegate, +// u16::MAX / 10 +// )); +// assert_ok!(SubtensorModule::add_stake( +// <::RuntimeOrigin>::signed(coldkey), +// delegate, +// 100 +// )); + +// assert_ok!(SubtensorModule::perform_swap_coldkey( +// &coldkey, +// &new_coldkey +// )); + + +// assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( +// <::RuntimeOrigin>::root(), +// to_be_set +// )); + +// assert_eq!( SubtensorModule::get_total_stake_for_hotkey( &delegate), 100 ); +// assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey), 0 ); +// assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &new_coldkey), 100 ); +// assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); +// assert_eq!( Stake::::get( delegate, coldkey ), 0 ); + + +// }); +// } + + + // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates --exact --nocapture #[test] fn test_sudo_hotfix_swap_coldkey_delegates() { new_test_ext(1).execute_with(|| { let new_coldkey = U256::from(0); - let owner = U256::from(1); let coldkey = U256::from(4); - let delegate = U256::from(2); - let netuid = 1u16; - add_network(netuid, 13, 0); - register_ok_neuron(netuid, delegate, owner, 0); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(owner), - delegate, - u16::MAX / 10 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey), - delegate, - 100 - )); - - assert_ok!(SubtensorModule::perform_swap_coldkey( - &coldkey, - &new_coldkey - )); - - - assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( - <::RuntimeOrigin>::root(), - to_be_set - )); - - assert_eq!( SubtensorModule::get_total_stake_for_hotkey( &delegate), 100 ); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey), 0 ); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &new_coldkey), 100 ); - assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); - assert_eq!( Stake::::get( delegate, coldkey ), 0 ); + assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates(<::RuntimeOrigin>::root(),coldkey,new_coldkey)); + }); +} +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates_with_broken_stake --exact --nocapture +#[test] +fn test_sudo_hotfix_swap_coldkey_delegates_with_broken_stake() { + new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(0); + let old_coldkey = U256::from(4); + let h1 = U256::from(5); + let h2 = U256::from(6); + let h3 = U256::from(7); + Stake::::insert( h3, old_coldkey, 100 ); + Stake::::insert( h2, old_coldkey, 100 ); + assert_eq!(Stake::::get( h3, old_coldkey ), 100 ); + assert_eq!(Stake::::get( h2, old_coldkey ), 100 ); + StakingHotkeys::::insert( new_coldkey, vec![h1, h2 ] ); + StakingHotkeys::::insert( old_coldkey, vec![ h3, h2] ); + assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates(<::RuntimeOrigin>::root(), old_coldkey, new_coldkey)); + let hotkeys = StakingHotkeys::::get( new_coldkey); + assert_eq!(hotkeys.len(), 3); + assert_eq!(hotkeys[0], h1); + assert_eq!(hotkeys[1], h2); + assert_eq!(hotkeys[2], h3); + let hotkeys_old = StakingHotkeys::::get( old_coldkey); + assert_eq!(hotkeys_old.len(), 0); + assert_eq!(Stake::::get( h3, old_coldkey ), 0 ); + assert_eq!(Stake::::get( h2, old_coldkey ), 0 ); + assert_eq!(Stake::::get( h3, new_coldkey ), 100 ); + assert_eq!(Stake::::get( h2, new_coldkey ), 100 ); }); } - diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b343ad0f0..d1fe2e0c9 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 162, + spec_version: 164, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From fc245f813114b13485f9d2dab9d7f981641ca487 Mon Sep 17 00:00:00 2001 From: const Date: Sat, 13 Jul 2024 02:05:15 -0500 Subject: [PATCH 161/185] remove staking hotkeys --- pallets/admin-utils/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 71b709a10..9a8744dc6 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -17,7 +17,6 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_support::traits::tokens::Balance; use frame_system::pallet_prelude::*; - use pallet_subtensor::StakingHotkeys; use sp_runtime::BoundedVec; /// The main data structure of the module. From 494796e59a305a00683ba55dab07bbc6526420b8 Mon Sep 17 00:00:00 2001 From: const Date: Sat, 13 Jul 2024 02:07:07 -0500 Subject: [PATCH 162/185] remove commented code --- pallets/subtensor/src/migration.rs | 50 ------------------------------ 1 file changed, 50 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 277c8e531..35b3c4260 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -603,53 +603,3 @@ pub fn migrate_populate_staking_hotkeys() -> Weight { Weight::zero() } } - -const LOG_TARGET_6: &str = "swapdelegate"; - -// const ALREADY_SWAPPED_COLDKEYS = []; - -// pub fn migrate_to_v6_swap_delegate_stake_also() -> Weight { -// let new_storage_version = 6; - -// // Check storage version -// let mut weight = T::DbWeight::get().reads(1); - -// // Grab current version -// let onchain_version = Pallet::::on_chain_storage_version(); - -// // Only runs if we haven't already updated version past above new_storage_version. -// if onchain_version < new_storage_version { -// info!(target: LOG_TARGET_6, ">>> Swapping delegate stake for an already-swapped coldkey {:?}", onchain_version); - -// for (old_coldkey, _) in ColdkeyArbitrationBlock::::iter() { -// // Get new coldkey -// let new_coldkey = ALREADY_SWAPPED_COLDKEYS.find(|x| x.0 == old_coldkey); -// // Grab hotkeys -// let staking_hotkeys = StakingHotkeys::::get(&old_coldkey); -// // Iterate over hotkeys -// for hotkey in staking_hotkeys { -// // Get the stake -// let stake = Stake::::get(&hotkey, &old_coldkey); -// Stake::::remove(&hotkey, &old_coldkey); - -// let old_stake = Stake::::get(&hotkey, &new_coldkey); -// // Add the stake to the new coldkey -// Stake::::insert(&hotkey, &new_coldkey, stake.saturating_add(old_stake)); -// weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); -// } - -// StakingHotkeys::::remove(&old_coldkey); -// weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); -// } - -// // Update storage version. -// StorageVersion::new(new_storage_version).put::>(); // Update to version so we don't run this again. -// // One write to storage version -// weight.saturating_accrue(T::DbWeight::get().writes(1)); - -// weight -// } else { -// info!(target: LOG_TARGET_6, "Migration to v6 already done!"); -// Weight::zero() -// } -// } From f041ed902c37f6040c0cee41e7643c844c8190bf Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 13 Jul 2024 10:40:19 -0400 Subject: [PATCH 163/185] cargo fmt --- pallets/subtensor/src/lib.rs | 16 ++-- pallets/subtensor/src/swap.rs | 138 ++++++++++++++++++-------------- pallets/subtensor/tests/swap.rs | 89 +++++++++++--------- 3 files changed, 135 insertions(+), 108 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 60d6c06d1..bded1705f 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2064,8 +2064,8 @@ pub mod pallet { /// The extrinsic for user to change its hotkey ///#[pallet::call_index(70)] ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) - ///.saturating_add(T::DbWeight::get().reads(272)) - ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + ///.saturating_add(T::DbWeight::get().reads(272)) + ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] ///pub fn swap_hotkey( /// origin: OriginFor, /// hotkey: T::AccountId, @@ -2255,18 +2255,18 @@ pub mod pallet { Self::user_remove_network(origin, netuid) } - /// Sets values for liquid alpha + /// Sets values for liquid alpha #[pallet::call_index(64)] #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_hotfix_swap_coldkey_delegates( origin: OriginFor, - old_coldkey: T::AccountId, - new_coldkey: T::AccountId, + old_coldkey: T::AccountId, + new_coldkey: T::AccountId, ) -> DispatchResult { ensure_root(origin)?; - Self::swap_hotfix(&old_coldkey, &new_coldkey); - Ok(()) - } + Self::swap_hotfix(&old_coldkey, &new_coldkey); + Ok(()) + } } // ---- Subtensor helper functions. diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index b8b0cc459..50e3ed944 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -862,33 +862,52 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } } - log::info!("Starting transfer of delegated stakes for old coldkey: {:?}", old_coldkey); - - for staking_hotkey in StakingHotkeys::::get(old_coldkey) { - log::info!("Processing staking hotkey: {:?}", staking_hotkey); - if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); - log::info!("Transferring delegated stake for hotkey {:?}: {}", hotkey, stake); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - total_transferred_stake = total_transferred_stake.saturating_add(stake); - log::info!("Updated stake for hotkey {:?} under new coldkey {:?}: {}", hotkey, new_coldkey, stake.saturating_add(old_stake)); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); - } - } else { - log::info!("No stake found for staking hotkey {:?} under old coldkey {:?}", staking_hotkey, old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - log::info!("Completed transfer of delegated stakes for old coldkey: {:?}", old_coldkey); + log::info!( + "Starting transfer of delegated stakes for old coldkey: {:?}", + old_coldkey + ); + + for staking_hotkey in StakingHotkeys::::get(old_coldkey) { + log::info!("Processing staking hotkey: {:?}", staking_hotkey); + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + log::info!( + "Transferring delegated stake for hotkey {:?}: {}", + hotkey, + stake + ); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + log::info!( + "Updated stake for hotkey {:?} under new coldkey {:?}: {}", + hotkey, + new_coldkey, + stake.saturating_add(old_stake) + ); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + } + } else { + log::info!( + "No stake found for staking hotkey {:?} under old coldkey {:?}", + staking_hotkey, + old_coldkey + ); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + + log::info!( + "Completed transfer of delegated stakes for old coldkey: {:?}", + old_coldkey + ); // Log the total transferred stake log::info!("Total transferred stake: {}", total_transferred_stake); @@ -917,7 +936,7 @@ impl Pallet { // Update the list of owned hotkeys for both old and new coldkeys - let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); for hotkey in old_owned_hotkeys { if !new_owned_hotkeys.contains(&hotkey) { new_owned_hotkeys.push(hotkey); @@ -925,13 +944,13 @@ impl Pallet { } OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); - OwnedHotkeys::::remove(old_coldkey); + OwnedHotkeys::::remove(old_coldkey); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Update the staking hotkeys for both old and new coldkeys - let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); + let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); - let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); for hotkey in staking_hotkeys { if !existing_staking_hotkeys.contains(&hotkey) { existing_staking_hotkeys.push(hotkey); @@ -953,37 +972,34 @@ impl Pallet { ); } + pub fn swap_hotfix(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { + let weight = T::DbWeight::get().reads_writes(2, 1); + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + for staking_hotkey in staking_hotkeys { + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + } + } + } + + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); - pub fn swap_hotfix(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { - - let weight = T::DbWeight::get().reads_writes(2, 1); - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - for staking_hotkey in staking_hotkeys { - if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - } - } - } - - let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); - - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - for hotkey in staking_hotkeys { - if !existing_staking_hotkeys.contains(&hotkey) { - existing_staking_hotkeys.push(hotkey); - } - } - StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); - StakingHotkeys::::remove(old_coldkey); - } + let staking_hotkeys = StakingHotkeys::::get(old_coldkey); + for hotkey in staking_hotkeys { + if !existing_staking_hotkeys.contains(&hotkey) { + existing_staking_hotkeys.push(hotkey); + } + } + StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); + StakingHotkeys::::remove(old_coldkey); + } /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. /// diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 2f075f729..6acf0b820 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1215,21 +1215,21 @@ fn test_swap_stake_for_coldkey() { let hotkey2 = U256::from(4); let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; - let stake_amount3 = 3000u64; + let stake_amount3 = 3000u64; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); // Setup initial state OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); - assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1 ); - assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1 ); + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1); + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1); - // Insert existing for same hotkey1 - Stake::::insert(hotkey1, new_coldkey, stake_amount3); - StakingHotkeys::::insert(new_coldkey, vec![hotkey1]); + // Insert existing for same hotkey1 + Stake::::insert(hotkey1, new_coldkey, stake_amount3); + StakingHotkeys::::insert(new_coldkey, vec![hotkey1]); TotalHotkeyStake::::insert(hotkey1, stake_amount1); TotalHotkeyStake::::insert(hotkey2, stake_amount2); @@ -1247,7 +1247,10 @@ fn test_swap_stake_for_coldkey() { SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); // Verify stake is additive, not replaced - assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1 + stake_amount3); + assert_eq!( + Stake::::get(hotkey1, new_coldkey), + stake_amount1 + stake_amount3 + ); // Verify ownership transfer assert_eq!( @@ -1299,7 +1302,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); - StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); TotalHotkeyStake::::insert(hotkey1, stake_amount1); TotalHotkeyStake::::insert(hotkey2, stake_amount2); TotalColdkeyStake::::insert(old_coldkey, total_stake); @@ -1311,10 +1314,12 @@ fn test_swap_staking_hotkeys_for_coldkey() { // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); - // Verify StakingHotkeys transfer - assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey1, hotkey2]); - assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); + assert_eq!( + StakingHotkeys::::get(new_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); }); } @@ -1330,11 +1335,11 @@ fn test_swap_delegated_stake_for_coldkey() { let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); - // Notice hotkey1 and hotkey2 are not in OwnedHotkeys - // coldkey therefore delegates stake to them + // Notice hotkey1 and hotkey2 are not in OwnedHotkeys + // coldkey therefore delegates stake to them // Setup initial state - StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); TotalHotkeyStake::::insert(hotkey1, stake_amount1); @@ -1872,11 +1877,14 @@ fn test_coldkey_delegations() { &coldkey, &new_coldkey )); - assert_eq!( SubtensorModule::get_total_stake_for_hotkey( &delegate), 100 ); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey), 0 ); - assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &new_coldkey), 100 ); - assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); - assert_eq!( Stake::::get( delegate, coldkey ), 0 ); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate), 100); + assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + 100 + ); + assert_eq!(Stake::::get(delegate, new_coldkey), 100); + assert_eq!(Stake::::get(delegate, coldkey), 0); }); } @@ -1908,7 +1916,6 @@ fn test_coldkey_delegations() { // &new_coldkey // )); - // assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( // <::RuntimeOrigin>::root(), // to_be_set @@ -1920,19 +1927,20 @@ fn test_coldkey_delegations() { // assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); // assert_eq!( Stake::::get( delegate, coldkey ), 0 ); - // }); // } - - // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates --exact --nocapture #[test] fn test_sudo_hotfix_swap_coldkey_delegates() { new_test_ext(1).execute_with(|| { let new_coldkey = U256::from(0); let coldkey = U256::from(4); - assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates(<::RuntimeOrigin>::root(),coldkey,new_coldkey)); + assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates( + <::RuntimeOrigin>::root(), + coldkey, + new_coldkey + )); }); } @@ -1945,24 +1953,27 @@ fn test_sudo_hotfix_swap_coldkey_delegates_with_broken_stake() { let h1 = U256::from(5); let h2 = U256::from(6); let h3 = U256::from(7); - Stake::::insert( h3, old_coldkey, 100 ); - Stake::::insert( h2, old_coldkey, 100 ); - assert_eq!(Stake::::get( h3, old_coldkey ), 100 ); - assert_eq!(Stake::::get( h2, old_coldkey ), 100 ); - StakingHotkeys::::insert( new_coldkey, vec![h1, h2 ] ); - StakingHotkeys::::insert( old_coldkey, vec![ h3, h2] ); - assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates(<::RuntimeOrigin>::root(), old_coldkey, new_coldkey)); - let hotkeys = StakingHotkeys::::get( new_coldkey); + Stake::::insert(h3, old_coldkey, 100); + Stake::::insert(h2, old_coldkey, 100); + assert_eq!(Stake::::get(h3, old_coldkey), 100); + assert_eq!(Stake::::get(h2, old_coldkey), 100); + StakingHotkeys::::insert(new_coldkey, vec![h1, h2]); + StakingHotkeys::::insert(old_coldkey, vec![h3, h2]); + assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates( + <::RuntimeOrigin>::root(), + old_coldkey, + new_coldkey + )); + let hotkeys = StakingHotkeys::::get(new_coldkey); assert_eq!(hotkeys.len(), 3); assert_eq!(hotkeys[0], h1); assert_eq!(hotkeys[1], h2); assert_eq!(hotkeys[2], h3); - let hotkeys_old = StakingHotkeys::::get( old_coldkey); + let hotkeys_old = StakingHotkeys::::get(old_coldkey); assert_eq!(hotkeys_old.len(), 0); - assert_eq!(Stake::::get( h3, old_coldkey ), 0 ); - assert_eq!(Stake::::get( h2, old_coldkey ), 0 ); - assert_eq!(Stake::::get( h3, new_coldkey ), 100 ); - assert_eq!(Stake::::get( h2, new_coldkey ), 100 ); - + assert_eq!(Stake::::get(h3, old_coldkey), 0); + assert_eq!(Stake::::get(h2, old_coldkey), 0); + assert_eq!(Stake::::get(h3, new_coldkey), 100); + assert_eq!(Stake::::get(h2, new_coldkey), 100); }); } From 54ec7502e32caf259a2470e5c8f20c5e9a0fe25a Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 13 Jul 2024 10:41:18 -0400 Subject: [PATCH 164/185] cargo fix --workspace --- pallets/subtensor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index bded1705f..83b6a4005 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -6,7 +6,6 @@ // pub use pallet::*; -use crate::system::ensure_root; use frame_system::{self as system, ensure_signed}; use frame_support::{ From ca51109e8136c562c3e9f9a0142df9b3ecfb7c23 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 13 Jul 2024 10:42:39 -0400 Subject: [PATCH 165/185] bump spec_version to 165 --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d1fe2e0c9..a4abd124f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 164, + spec_version: 165, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 4dafd88d31cd84b97078820a0d83bf473e4e7b6c Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 10:07:24 -0500 Subject: [PATCH 166/185] swap hotkey benchmark removed --- pallets/subtensor/src/benchmarks.rs | 54 ++++++++++++++--------------- runtime/src/lib.rs | 2 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 80a375d10..03e087a92 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -314,33 +314,33 @@ benchmarks! { assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); }: dissolve_network(RawOrigin::Signed(coldkey), 1) - swap_hotkey { - let seed: u32 = 1; - let coldkey: T::AccountId = account("Alice", 0, seed); - let old_hotkey: T::AccountId = account("Bob", 0, seed); - let new_hotkey: T::AccountId = account("Charlie", 0, seed); - - let netuid = 1u16; - Subtensor::::init_new_network(netuid, 100); - Subtensor::::set_min_burn(netuid, 1); - Subtensor::::set_max_burn(netuid, 1); - Subtensor::::set_target_registrations_per_interval(netuid, 256); - Subtensor::::set_max_registrations_per_block(netuid, 256); - - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); - assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, old_hotkey.clone())); - assert_ok!(Subtensor::::become_delegate(RawOrigin::Signed(coldkey.clone()).into(), old_hotkey.clone())); - - let max_uids = Subtensor::::get_max_allowed_uids(netuid) as u32; - for i in 0..max_uids - 1 { - let coldkey: T::AccountId = account("Axon", 0, i); - let hotkey: T::AccountId = account("Hotkey", 0, i); - - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); - assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey)); - assert_ok!(Subtensor::::add_stake(RawOrigin::Signed(coldkey).into(), old_hotkey.clone(), 1_000_000_000)); - } - }: _(RawOrigin::Signed(coldkey), old_hotkey, new_hotkey) + // swap_hotkey { + // let seed: u32 = 1; + // let coldkey: T::AccountId = account("Alice", 0, seed); + // let old_hotkey: T::AccountId = account("Bob", 0, seed); + // let new_hotkey: T::AccountId = account("Charlie", 0, seed); + + // let netuid = 1u16; + // Subtensor::::init_new_network(netuid, 100); + // Subtensor::::set_min_burn(netuid, 1); + // Subtensor::::set_max_burn(netuid, 1); + // Subtensor::::set_target_registrations_per_interval(netuid, 256); + // Subtensor::::set_max_registrations_per_block(netuid, 256); + + // Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); + // assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, old_hotkey.clone())); + // assert_ok!(Subtensor::::become_delegate(RawOrigin::Signed(coldkey.clone()).into(), old_hotkey.clone())); + + // let max_uids = Subtensor::::get_max_allowed_uids(netuid) as u32; + // for i in 0..max_uids - 1 { + // let coldkey: T::AccountId = account("Axon", 0, i); + // let hotkey: T::AccountId = account("Hotkey", 0, i); + + // Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); + // assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey)); + // assert_ok!(Subtensor::::add_stake(RawOrigin::Signed(coldkey).into(), old_hotkey.clone(), 1_000_000_000)); + // } + // }: _(RawOrigin::Signed(coldkey), old_hotkey, new_hotkey) commit_weights { let tempo: u16 = 1; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..d1fe2e0c9 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 165, + spec_version: 164, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From d95a7df24c1f917b695e2869ac923e73d28499c5 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 10:43:12 -0500 Subject: [PATCH 167/185] add migration and tests for total coldkey hotfix --- pallets/subtensor/src/lib.rs | 12 ++-- pallets/subtensor/src/migration.rs | 57 +++++++++++++++++ pallets/subtensor/src/swap.rs | 29 --------- pallets/subtensor/tests/migration.rs | 60 ++++++++++++++++++ pallets/subtensor/tests/swap.rs | 92 +--------------------------- runtime/src/lib.rs | 2 +- 6 files changed, 125 insertions(+), 127 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 83b6a4005..8d713d76a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1419,7 +1419,9 @@ pub mod pallet { // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. .saturating_add(migration::migrate_populate_owned::()) // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. - .saturating_add(migration::migrate_populate_staking_hotkeys::()); + .saturating_add(migration::migrate_populate_staking_hotkeys::()) + // Fix total coldkey stake. + .saturating_add(migration::migrate_fix_total_coldkey_stake::()); weight } @@ -2258,12 +2260,10 @@ pub mod pallet { #[pallet::call_index(64)] #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_hotfix_swap_coldkey_delegates( - origin: OriginFor, - old_coldkey: T::AccountId, - new_coldkey: T::AccountId, + _origin: OriginFor, + _old_coldkey: T::AccountId, + _new_coldkey: T::AccountId, ) -> DispatchResult { - ensure_root(origin)?; - Self::swap_hotfix(&old_coldkey, &new_coldkey); Ok(()) } } diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 35b3c4260..d03bae49f 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -24,6 +24,63 @@ pub mod deprecated_loaded_emission_format { StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; } + +/// Migrates and fixes the total coldkey stake. +/// +/// This function iterates through all staking hotkeys, calculates the total stake for each coldkey, +/// and updates the `TotalColdkeyStake` storage accordingly. The migration is only performed if the +/// on-chain storage version is 6. +/// +/// # Returns +/// The weight of the migration process. +pub fn do_migrate_fix_total_coldkey_stake() -> Weight{ + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Iterate through all staking hotkeys. + for (coldkey, hotkey_vec) in StakingHotkeys::::iter() { + // Init the zero value. + let mut coldkey_stake_sum: u64 = 0; + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // Calculate the total stake for the current coldkey. + for hotkey in hotkey_vec { + // Cant fail on retrieval. + coldkey_stake_sum = coldkey_stake_sum.saturating_add(Stake::::get(hotkey, coldkey.clone())); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + // Update the `TotalColdkeyStake` storage with the calculated stake sum. + // Cant fail on insert. + TotalColdkeyStake::::insert( coldkey.clone(), coldkey_stake_sum ); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + weight +} +// Public migrate function to be called by Lib.rs on upgrade. +pub fn migrate_fix_total_coldkey_stake() -> Weight { + + let current_storage_version: u16 = 6; + let next_storage_version: u16 = 7; + + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Grab the current on-chain storage version. + // Cant fail on retrieval. + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only run this migration on storage version 6. + if onchain_version == current_storage_version { + weight = weight.saturating_add( do_migrate_fix_total_coldkey_stake::() ); + // Cant fail on insert. + StorageVersion::new( next_storage_version ).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + + // Return the migration weight. + weight +} + /// Performs migration to update the total issuance based on the sum of stakes and total balances. /// This migration is applicable only if the current storage version is 5, after which it updates the storage version to 6. /// diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 50e3ed944..8e4ca5cc9 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -972,35 +972,6 @@ impl Pallet { ); } - pub fn swap_hotfix(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { - let weight = T::DbWeight::get().reads_writes(2, 1); - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - for staking_hotkey in staking_hotkeys { - if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - } - } - } - - let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); - - let staking_hotkeys = StakingHotkeys::::get(old_coldkey); - for hotkey in staking_hotkeys { - if !existing_staking_hotkeys.contains(&hotkey) { - existing_staking_hotkeys.push(hotkey); - } - } - StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); - StakingHotkeys::::remove(old_coldkey); - } - /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. /// /// # Arguments diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index c921124f4..3ddf68502 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -3,6 +3,7 @@ mod mock; use frame_support::assert_ok; use frame_system::Config; +use pallet_subtensor::*; use mock::*; use sp_core::U256; @@ -276,3 +277,62 @@ fn test_migration_delete_subnet_21() { assert!(!SubtensorModule::if_subnet_exist(21)); }) } + + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 0); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_value_already_in_total --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 100000000); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_no_entry --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_no_entry() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(0); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 100000000); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + assert_eq!(TotalColdkeyStake::::get(coldkey), 0); + }) +} + + diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 6acf0b820..80ac32e7a 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1886,94 +1886,4 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, new_coldkey), 100); assert_eq!(Stake::::get(delegate, coldkey), 0); }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates --exact --nocapture -// #[test] -// fn test_sudo_hotfix_swap_coldkey_delegates() { -// new_test_ext(1).execute_with(|| { -// let new_coldkey = U256::from(0); -// let owner = U256::from(1); -// let coldkey = U256::from(4); -// let delegate = U256::from(2); -// let netuid = 1u16; -// add_network(netuid, 13, 0); -// register_ok_neuron(netuid, delegate, owner, 0); -// SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); -// assert_ok!(SubtensorModule::do_become_delegate( -// <::RuntimeOrigin>::signed(owner), -// delegate, -// u16::MAX / 10 -// )); -// assert_ok!(SubtensorModule::add_stake( -// <::RuntimeOrigin>::signed(coldkey), -// delegate, -// 100 -// )); - -// assert_ok!(SubtensorModule::perform_swap_coldkey( -// &coldkey, -// &new_coldkey -// )); - -// assert_ok!(AdminUtils::sudo_hotfix_swap_coldkey_delegates( -// <::RuntimeOrigin>::root(), -// to_be_set -// )); - -// assert_eq!( SubtensorModule::get_total_stake_for_hotkey( &delegate), 100 ); -// assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey), 0 ); -// assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &new_coldkey), 100 ); -// assert_eq!( Stake::::get( delegate, new_coldkey ), 100 ); -// assert_eq!( Stake::::get( delegate, coldkey ), 0 ); - -// }); -// } - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates --exact --nocapture -#[test] -fn test_sudo_hotfix_swap_coldkey_delegates() { - new_test_ext(1).execute_with(|| { - let new_coldkey = U256::from(0); - let coldkey = U256::from(4); - assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates( - <::RuntimeOrigin>::root(), - coldkey, - new_coldkey - )); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_sudo_hotfix_swap_coldkey_delegates_with_broken_stake --exact --nocapture -#[test] -fn test_sudo_hotfix_swap_coldkey_delegates_with_broken_stake() { - new_test_ext(1).execute_with(|| { - let new_coldkey = U256::from(0); - let old_coldkey = U256::from(4); - let h1 = U256::from(5); - let h2 = U256::from(6); - let h3 = U256::from(7); - Stake::::insert(h3, old_coldkey, 100); - Stake::::insert(h2, old_coldkey, 100); - assert_eq!(Stake::::get(h3, old_coldkey), 100); - assert_eq!(Stake::::get(h2, old_coldkey), 100); - StakingHotkeys::::insert(new_coldkey, vec![h1, h2]); - StakingHotkeys::::insert(old_coldkey, vec![h3, h2]); - assert_ok!(SubtensorModule::sudo_hotfix_swap_coldkey_delegates( - <::RuntimeOrigin>::root(), - old_coldkey, - new_coldkey - )); - let hotkeys = StakingHotkeys::::get(new_coldkey); - assert_eq!(hotkeys.len(), 3); - assert_eq!(hotkeys[0], h1); - assert_eq!(hotkeys[1], h2); - assert_eq!(hotkeys[2], h3); - let hotkeys_old = StakingHotkeys::::get(old_coldkey); - assert_eq!(hotkeys_old.len(), 0); - assert_eq!(Stake::::get(h3, old_coldkey), 0); - assert_eq!(Stake::::get(h2, old_coldkey), 0); - assert_eq!(Stake::::get(h3, new_coldkey), 100); - assert_eq!(Stake::::get(h2, new_coldkey), 100); - }); -} +} \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d1fe2e0c9..a4abd124f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 164, + spec_version: 165, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 5e6fc7ec403ac61566414932f8ed98b616520fb0 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 10:46:53 -0500 Subject: [PATCH 168/185] adds new test for missing hotkey value --- pallets/subtensor/tests/migration.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 3ddf68502..feee91e23 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -335,4 +335,18 @@ fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { }) } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 100000000); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + assert_eq!(TotalColdkeyStake::::get(coldkey), 20000); + }) +} + From 3c61aa82c79089d51e0f99a1a289b680f7b73fee Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 11:02:13 -0500 Subject: [PATCH 169/185] bump migration values --- pallets/subtensor/src/migration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index d03bae49f..362bdf009 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -59,8 +59,8 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight{ // Public migrate function to be called by Lib.rs on upgrade. pub fn migrate_fix_total_coldkey_stake() -> Weight { - let current_storage_version: u16 = 6; - let next_storage_version: u16 = 7; + let current_storage_version: u16 = 7; + let next_storage_version: u16 = 8; // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); From ff23b6fc576fdfa353a570bffee49d22ea6cfc41 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 11:08:33 -0500 Subject: [PATCH 170/185] fmt --- pallets/subtensor/src/migration.rs | 17 ++++++++--------- pallets/subtensor/tests/migration.rs | 15 ++++++--------- pallets/subtensor/tests/swap.rs | 2 +- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 362bdf009..72a86ea4e 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -24,7 +24,6 @@ pub mod deprecated_loaded_emission_format { StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; } - /// Migrates and fixes the total coldkey stake. /// /// This function iterates through all staking hotkeys, calculates the total stake for each coldkey, @@ -33,7 +32,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Returns /// The weight of the migration process. -pub fn do_migrate_fix_total_coldkey_stake() -> Weight{ +pub fn do_migrate_fix_total_coldkey_stake() -> Weight { // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); @@ -46,19 +45,19 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight{ // Calculate the total stake for the current coldkey. for hotkey in hotkey_vec { // Cant fail on retrieval. - coldkey_stake_sum = coldkey_stake_sum.saturating_add(Stake::::get(hotkey, coldkey.clone())); + coldkey_stake_sum = + coldkey_stake_sum.saturating_add(Stake::::get(hotkey, coldkey.clone())); weight = weight.saturating_add(T::DbWeight::get().reads(1)); } // Update the `TotalColdkeyStake` storage with the calculated stake sum. // Cant fail on insert. - TotalColdkeyStake::::insert( coldkey.clone(), coldkey_stake_sum ); + TotalColdkeyStake::::insert(coldkey.clone(), coldkey_stake_sum); weight = weight.saturating_add(T::DbWeight::get().writes(1)); } weight } // Public migrate function to be called by Lib.rs on upgrade. pub fn migrate_fix_total_coldkey_stake() -> Weight { - let current_storage_version: u16 = 7; let next_storage_version: u16 = 8; @@ -67,15 +66,15 @@ pub fn migrate_fix_total_coldkey_stake() -> Weight { // Grab the current on-chain storage version. // Cant fail on retrieval. - let onchain_version = Pallet::::on_chain_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); // Only run this migration on storage version 6. if onchain_version == current_storage_version { - weight = weight.saturating_add( do_migrate_fix_total_coldkey_stake::() ); + weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); // Cant fail on insert. - StorageVersion::new( next_storage_version ).put::>(); + StorageVersion::new(next_storage_version).put::>(); weight.saturating_accrue(T::DbWeight::get().writes(1)); - } + } // Return the migration weight. weight diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index feee91e23..360568235 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -3,8 +3,8 @@ mod mock; use frame_support::assert_ok; use frame_system::Config; -use pallet_subtensor::*; use mock::*; +use pallet_subtensor::*; use sp_core::U256; #[test] @@ -278,12 +278,11 @@ fn test_migration_delete_subnet_21() { }) } - // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake --exact --nocapture #[test] fn test_migrate_fix_total_coldkey_stake() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(0); + let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); @@ -298,7 +297,7 @@ fn test_migrate_fix_total_coldkey_stake() { #[test] fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(0); + let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); @@ -313,7 +312,7 @@ fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { #[test] fn test_migrate_fix_total_coldkey_stake_no_entry() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(0); + let coldkey = U256::from(0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); @@ -327,7 +326,7 @@ fn test_migrate_fix_total_coldkey_stake_no_entry() { #[test] fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(0); + let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); @@ -339,7 +338,7 @@ fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { #[test] fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(0); + let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); @@ -348,5 +347,3 @@ fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { assert_eq!(TotalColdkeyStake::::get(coldkey), 20000); }) } - - diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 80ac32e7a..21c3a983a 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -1886,4 +1886,4 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, new_coldkey), 100); assert_eq!(Stake::::get(delegate, coldkey), 0); }); -} \ No newline at end of file +} From b3fa4876355ae0a4de6378f532a8c92cbb28ae7e Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 16 Jul 2024 23:41:50 +0400 Subject: [PATCH 171/185] feat: try runtime passing --- pallets/subtensor/src/lib.rs | 7 ++- pallets/subtensor/src/migration.rs | 52 ++++++++++++++------ pallets/subtensor/tests/migration.rs | 73 +++++++++++++++++++++++++--- 3 files changed, 110 insertions(+), 22 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8d713d76a..0a5c20942 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -84,7 +84,7 @@ pub mod pallet { /// Tracks version for migrations. Should be monotonic with respect to the /// order of migrations. (i.e. always increasing) - const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); /// Minimum balance required to perform a coldkey swap pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 100_000_000; // 0.1 TAO in RAO @@ -1140,6 +1140,9 @@ pub mod pallet { DefaultBonds, >; + #[pallet::storage] // --- Storage for migration run status + pub type HasMigrationRun = StorageMap<_, Identity, Vec, bool, ValueQuery>; + /// ================== /// ==== Genesis ===== /// ================== @@ -1420,7 +1423,7 @@ pub mod pallet { .saturating_add(migration::migrate_populate_owned::()) // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. .saturating_add(migration::migrate_populate_staking_hotkeys::()) - // Fix total coldkey stake. + //Fix total coldkey stake. .saturating_add(migration::migrate_fix_total_coldkey_stake::()); weight diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 72a86ea4e..bfbed513c 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -56,30 +56,54 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight { } weight } -// Public migrate function to be called by Lib.rs on upgrade. + +// #[pallet::storage] +// #[pallet::getter(fn has_migration_run)] +// pub type HasMigrationRun = StorageMap<_, Identity, &'static str, bool, ValueQuery>; + +/// Migrates and fixes the total coldkey stake. +/// +/// This function checks if the migration has already run, and if not, it performs the migration +/// to fix the total coldkey stake. It also marks the migration as completed after running. +/// +/// # Returns +/// The weight of the migration process. pub fn migrate_fix_total_coldkey_stake() -> Weight { - let current_storage_version: u16 = 7; - let next_storage_version: u16 = 8; + let migration_name = b"fix_total_coldkey_stake_v7".to_vec(); // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); - // Grab the current on-chain storage version. - // Cant fail on retrieval. - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only run this migration on storage version 6. - if onchain_version == current_storage_version { - weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); - // Cant fail on insert. - StorageVersion::new(next_storage_version).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); + // Check if the migration has already run + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + migration_name + ); + return Weight::zero(); } + log::info!("Running migration '{:?}'", migration_name); + + // Run the migration + weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); + + // Mark the migration as completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Set the storage version to 7 + StorageVersion::new(7).put::>(); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed. Storage version set to 7.", + migration_name + ); + // Return the migration weight. weight } - /// Performs migration to update the total issuance based on the sum of stakes and total balances. /// This migration is applicable only if the current storage version is 5, after which it updates the storage version to 6. /// diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 360568235..6d019562f 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -1,7 +1,7 @@ #![allow(clippy::unwrap_used)] mod mock; -use frame_support::assert_ok; +use frame_support::{assert_ok, weights::Weight}; use frame_system::Config; use mock::*; use pallet_subtensor::*; @@ -278,17 +278,25 @@ fn test_migration_delete_subnet_21() { }) } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake --exact --nocapture // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake --exact --nocapture #[test] fn test_migrate_fix_total_coldkey_stake() { new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); Stake::::insert(U256::from(3), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + + let second_weight = run_migration_and_check(migration_name); + assert_eq!(second_weight, Weight::zero()); assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); }) } @@ -297,13 +305,16 @@ fn test_migrate_fix_total_coldkey_stake() { #[test] fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); Stake::::insert(U256::from(3), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); }) } @@ -312,12 +323,15 @@ fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { #[test] fn test_migrate_fix_total_coldkey_stake_no_entry() { new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); Stake::::insert(U256::from(3), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); }) } @@ -326,10 +340,13 @@ fn test_migrate_fix_total_coldkey_stake_no_entry() { #[test] fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); assert_eq!(TotalColdkeyStake::::get(coldkey), 0); }) } @@ -338,12 +355,56 @@ fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { #[test] fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); assert_eq!(TotalColdkeyStake::::get(coldkey), 20000); }) } + +// New test to check if migration runs only once +#[test] +fn test_migrate_fix_total_coldkey_stake_runs_once() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 0); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + + // First run + let first_weight = run_migration_and_check(migration_name); + assert!(first_weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + + // Second run + let second_weight = run_migration_and_check(migration_name); + assert_eq!(second_weight, Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +fn run_migration_and_check(migration_name: &'static str) -> frame_support::weights::Weight { + // Execute the migration and store its weight + let weight: frame_support::weights::Weight = + pallet_subtensor::migration::migrate_fix_total_coldkey_stake::(); + + // Check if the migration has been marked as completed + assert!(HasMigrationRun::::get( + migration_name.as_bytes().to_vec() + )); + + // Return the weight of the executed migration + weight +} + +// TODO: Consider adding error handling for cases where the migration fails or is already completed. +// NOTE: The use of `as_bytes().to_vec()` might be inefficient for larger strings. Consider using a more efficient encoding method if performance becomes an issue. From 8766c98f5058e7e66252cb04866fb8ed3f770c0f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 16 Jul 2024 23:46:52 +0400 Subject: [PATCH 172/185] chore: remove commented code --- pallets/subtensor/src/migration.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index bfbed513c..3cc1a33ab 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -57,10 +57,6 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight { weight } -// #[pallet::storage] -// #[pallet::getter(fn has_migration_run)] -// pub type HasMigrationRun = StorageMap<_, Identity, &'static str, bool, ValueQuery>; - /// Migrates and fixes the total coldkey stake. /// /// This function checks if the migration has already run, and if not, it performs the migration From 92fc1f042e2ffbd2521d169ecd870fcfe6b7b722 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 16 Jul 2024 23:54:32 +0400 Subject: [PATCH 173/185] chore: make logs human readable --- pallets/subtensor/src/migration.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 3cc1a33ab..20e91237f 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -1,4 +1,5 @@ use super::*; +use alloc::string::String; use frame_support::traits::DefensiveResult; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, @@ -79,7 +80,10 @@ pub fn migrate_fix_total_coldkey_stake() -> Weight { return Weight::zero(); } - log::info!("Running migration '{:?}'", migration_name); + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); // Run the migration weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); @@ -94,7 +98,7 @@ pub fn migrate_fix_total_coldkey_stake() -> Weight { log::info!( "Migration '{:?}' completed. Storage version set to 7.", - migration_name + String::from_utf8_lossy(&migration_name) ); // Return the migration weight. From c255d92f812a3e241870cf1c16675ffec668da09 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 16 Jul 2024 23:57:04 +0400 Subject: [PATCH 174/185] chore: remove comments --- pallets/subtensor/tests/migration.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 6d019562f..8323cab8a 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -405,6 +405,3 @@ fn run_migration_and_check(migration_name: &'static str) -> frame_support::weigh // Return the weight of the executed migration weight } - -// TODO: Consider adding error handling for cases where the migration fails or is already completed. -// NOTE: The use of `as_bytes().to_vec()` might be inefficient for larger strings. Consider using a more efficient encoding method if performance becomes an issue. From 04785968b89a70eac965a8c8d9470e8289a613fc Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:01:29 +0400 Subject: [PATCH 175/185] Update pallets/subtensor/src/lib.rs Co-authored-by: Sam Johnson --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0a5c20942..f7824e2a3 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1423,7 +1423,7 @@ pub mod pallet { .saturating_add(migration::migrate_populate_owned::()) // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. .saturating_add(migration::migrate_populate_staking_hotkeys::()) - //Fix total coldkey stake. + // Fix total coldkey stake. .saturating_add(migration::migrate_fix_total_coldkey_stake::()); weight From 8f6e8d63caefaebc56a6a4ea309bbd0dc1595253 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 18:02:54 -0400 Subject: [PATCH 176/185] add test first --- pallets/subtensor/tests/migration.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 8323cab8a..088e00bc9 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -392,6 +392,23 @@ fn test_migrate_fix_total_coldkey_stake_runs_once() { }) } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_starts_with_value_no_more_entries --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_starts_with_value_no_more_entries() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 123_456_789); + + // Notably, coldkey has no stake map or staking_hotkeys map entries + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + // Therefore 0 + assert_eq!(TotalColdkeyStake::::get(coldkey), 0); + }) +} + fn run_migration_and_check(migration_name: &'static str) -> frame_support::weights::Weight { // Execute the migration and store its weight let weight: frame_support::weights::Weight = From f25bdda714de3f569a4e7a107cedb45e7716b77a Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 18:06:00 -0400 Subject: [PATCH 177/185] clear map before rest of migration --- pallets/subtensor/src/migration.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 20e91237f..82cd8d0b6 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -37,6 +37,9 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight { // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); + // Clear everything from the map first + let _ = TotalColdkeyStake::::clear(u32::MAX, None); + // Iterate through all staking hotkeys. for (coldkey, hotkey_vec) in StakingHotkeys::::iter() { // Init the zero value. From e009ff13b64174533504898d93c2140e547fe399 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 18:07:54 -0400 Subject: [PATCH 178/185] track weight for clear/removal --- pallets/subtensor/src/migration.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 82cd8d0b6..e7e2b9795 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -38,7 +38,11 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight { let mut weight = T::DbWeight::get().reads(1); // Clear everything from the map first - let _ = TotalColdkeyStake::::clear(u32::MAX, None); + let removal_results = TotalColdkeyStake::::clear(u32::MAX, None); + // 1 read/write per removal + let entries_removed: u64 = removal_results.backend.into(); + weight = + weight.saturating_add(T::DbWeight::get().reads_writes(entries_removed, entries_removed)); // Iterate through all staking hotkeys. for (coldkey, hotkey_vec) in StakingHotkeys::::iter() { From 480b1c5fb9f4336d30131fa9d02d2c36e0a80aea Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 18:10:12 -0400 Subject: [PATCH 179/185] add comment --- pallets/subtensor/src/migration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index e7e2b9795..866ff08fd 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -37,7 +37,7 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight { // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); - // Clear everything from the map first + // Clear everything from the map first, no limit (u32::MAX) let removal_results = TotalColdkeyStake::::clear(u32::MAX, None); // 1 read/write per removal let entries_removed: u64 = removal_results.backend.into(); From eb4a57cd15f8ed3b798a787903c02c7952634328 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 18:14:25 -0400 Subject: [PATCH 180/185] rename test --- pallets/subtensor/tests/migration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 088e00bc9..d47155862 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -392,9 +392,9 @@ fn test_migrate_fix_total_coldkey_stake_runs_once() { }) } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_starts_with_value_no_more_entries --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_starts_with_value_no_stake_map_entries --exact --nocapture #[test] -fn test_migrate_fix_total_coldkey_stake_starts_with_value_no_more_entries() { +fn test_migrate_fix_total_coldkey_stake_starts_with_value_no_stake_map_entries() { new_test_ext(1).execute_with(|| { let migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); From 23f540c331959cae2441ebc8c88aa191c8f0c31c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 19:07:34 -0400 Subject: [PATCH 181/185] add test for emission appends staking hotkeys map --- pallets/subtensor/tests/staking.rs | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 5db439e5b..cb2cac4ef 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -4719,3 +4719,35 @@ fn test_do_schedule_coldkey_swap_regular_user_passes_min_balance() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_emission_creates_staking_hotkeys_entry --exact --nocapture +#[test] +fn test_emission_creates_staking_hotkeys_entry() { + new_test_ext(1).execute_with(|| { + let hotkey0 = U256::from(1); + let hotkey1 = U256::from(2); + + let coldkey = U256::from(3); + + // Add to Owner map + Owner::::insert(hotkey0, coldkey); + Owner::::insert(hotkey1, coldkey); + OwnedHotkeys::::insert(coldkey, vec![hotkey0, hotkey1]); + + // Emit through hotkey + SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 1_000); + + // Verify StakingHotkeys has an entry + assert_eq!(StakingHotkeys::::get(coldkey).len(), 1); + assert!(StakingHotkeys::::get(coldkey).contains(&hotkey0)); + + // Try again with another emission on hotkey1 + SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 2_000); + + // Verify both hotkeys are now in the map + assert_eq!(StakingHotkeys::::get(coldkey).len(), 2); + let final_map = StakingHotkeys::::get(coldkey); + assert!(final_map.contains(&hotkey0)); + assert!(final_map.contains(&hotkey1)); + }) +} From aa5e890dab6e2f55f419680e5316806c1feb2f2d Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 19 Jul 2024 09:02:42 -0400 Subject: [PATCH 182/185] Merge main into devnet ready 7 18 2024 (#650) * draft: hotkey swap for senate * feat: remove schedule coldkey swap * chore: fix tests * fixes for network resumption * chore: bump spec * fix: add back arbitration check * fix: hotkey * fix: coldkey arb swap hotkey * swap delegate stake also * add test for this * swap over stakinghotkeys map * check map first * add staking hotkeys test * no take * keep old stake * add check to test * fix some tests * fix delegate test * update staking hotekys maps * init * comment out * add admin swap * fix swap * .. * hotkey staking maps fix * remove staking hotkeys * remove commented code * cargo fmt * cargo fix --workspace * bump spec_version to 165 * swap hotkey benchmark removed * add migration and tests for total coldkey hotfix * adds new test for missing hotkey value * bump migration values * fmt * feat: try runtime passing * chore: remove commented code * chore: make logs human readable * chore: remove comments * Update pallets/subtensor/src/lib.rs Co-authored-by: Sam Johnson * add test first * clear map before rest of migration * track weight for clear/removal * add comment * rename test * add test for emission appends staking hotkeys map * Update runtime/src/lib.rs --------- Co-authored-by: Samuel Dare Co-authored-by: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Co-authored-by: const Co-authored-by: Sam Johnson Co-authored-by: Unconst <32490803+unconst@users.noreply.github.com> --- pallets/admin-utils/tests/mock.rs | 4 +- pallets/subtensor/src/benchmarks.rs | 80 +++---- pallets/subtensor/src/lib.rs | 46 ++-- pallets/subtensor/src/migration.rs | 87 ++++++++ pallets/subtensor/src/swap.rs | 130 +++++++++-- pallets/subtensor/src/utils.rs | 4 +- pallets/subtensor/tests/migration.rs | 148 +++++++++++- pallets/subtensor/tests/mock.rs | 4 +- pallets/subtensor/tests/registration.rs | 284 ++++++++++++------------ pallets/subtensor/tests/staking.rs | 32 +++ pallets/subtensor/tests/swap.rs | 252 ++++++++++++++++++--- runtime/src/lib.rs | 9 +- 12 files changed, 819 insertions(+), 261 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 5f71626ad..dbf88bdfa 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -110,7 +110,7 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 1; - pub const InitialHotkeySwapCost: u64 = 1_000_000_000; + pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -166,7 +166,7 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; - type HotkeySwapCost = InitialHotkeySwapCost; + type KeySwapCost = InitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index ee7a437a9..c6c9ac481 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -314,33 +314,33 @@ benchmarks! { assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); }: dissolve_network(RawOrigin::Signed(coldkey), 1) - swap_hotkey { - let seed: u32 = 1; - let coldkey: T::AccountId = account("Alice", 0, seed); - let old_hotkey: T::AccountId = account("Bob", 0, seed); - let new_hotkey: T::AccountId = account("Charlie", 0, seed); - - let netuid = 1u16; - Subtensor::::init_new_network(netuid, 100); - Subtensor::::set_min_burn(netuid, 1); - Subtensor::::set_max_burn(netuid, 1); - Subtensor::::set_target_registrations_per_interval(netuid, 256); - Subtensor::::set_max_registrations_per_block(netuid, 256); - - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); - assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, old_hotkey.clone())); - assert_ok!(Subtensor::::become_delegate(RawOrigin::Signed(coldkey.clone()).into(), old_hotkey.clone())); - - let max_uids = Subtensor::::get_max_allowed_uids(netuid) as u32; - for i in 0..max_uids - 1 { - let coldkey: T::AccountId = account("Axon", 0, i); - let hotkey: T::AccountId = account("Hotkey", 0, i); - - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); - assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey)); - assert_ok!(Subtensor::::add_stake(RawOrigin::Signed(coldkey).into(), old_hotkey.clone(), 1_000_000_000)); - } - }: _(RawOrigin::Signed(coldkey), old_hotkey, new_hotkey) + // swap_hotkey { + // let seed: u32 = 1; + // let coldkey: T::AccountId = account("Alice", 0, seed); + // let old_hotkey: T::AccountId = account("Bob", 0, seed); + // let new_hotkey: T::AccountId = account("Charlie", 0, seed); + + // let netuid = 1u16; + // Subtensor::::init_new_network(netuid, 100); + // Subtensor::::set_min_burn(netuid, 1); + // Subtensor::::set_max_burn(netuid, 1); + // Subtensor::::set_target_registrations_per_interval(netuid, 256); + // Subtensor::::set_max_registrations_per_block(netuid, 256); + + // Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); + // assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, old_hotkey.clone())); + // assert_ok!(Subtensor::::become_delegate(RawOrigin::Signed(coldkey.clone()).into(), old_hotkey.clone())); + + // let max_uids = Subtensor::::get_max_allowed_uids(netuid) as u32; + // for i in 0..max_uids - 1 { + // let coldkey: T::AccountId = account("Axon", 0, i); + // let hotkey: T::AccountId = account("Hotkey", 0, i); + + // Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 10_000_000_000u64); + // assert_ok!(Subtensor::::burned_register(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey)); + // assert_ok!(Subtensor::::add_stake(RawOrigin::Signed(coldkey).into(), old_hotkey.clone(), 1_000_000_000)); + // } + // }: _(RawOrigin::Signed(coldkey), old_hotkey, new_hotkey) commit_weights { let tempo: u16 = 1; @@ -429,30 +429,4 @@ reveal_weights { }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) - schedule_coldkey_swap { - let seed: u32 = 1; - let old_coldkey: T::AccountId = account("OldColdkey", 0, seed); - let new_coldkey: T::AccountId = account("NewColdkey", 0, seed + 1); - let hotkey: T::AccountId = account("Hotkey", 0, seed); - - let netuid = 1u16; - let tempo = 1u16; - let block_number: u64 = Subtensor::::get_current_block_as_u64(); - let nonce = 0; - - // Initialize the network - Subtensor::::init_new_network(netuid, tempo); - Subtensor::::set_network_registration_allowed(netuid, true); - - // Add balance to the old coldkey account - let amount_to_be_staked: u64 = 1000000u32.into(); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey.clone(), amount_to_be_staked+1000000000); - // Burned register the hotkey with the old coldkey - assert_ok!(Subtensor::::burned_register( - RawOrigin::Signed(old_coldkey.clone()).into(), - netuid, - hotkey.clone() - )); - - }: schedule_coldkey_swap(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone(), vec![], block_number, nonce) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 240e86a57..0f10bc581 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -84,7 +84,7 @@ pub mod pallet { /// Tracks version for migrations. Should be monotonic with respect to the /// order of migrations. (i.e. always increasing) - const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); /// Minimum balance required to perform a coldkey swap pub const MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP: u64 = 100_000_000; // 0.1 TAO in RAO @@ -247,7 +247,7 @@ pub mod pallet { type InitialTargetStakesPerInterval: Get; /// Cost of swapping a hotkey. #[pallet::constant] - type HotkeySwapCost: Get; + type KeySwapCost: Get; /// The upper bound for the alpha parameter. Used for Liquid Alpha. #[pallet::constant] type AlphaHigh: Get; @@ -1140,6 +1140,9 @@ pub mod pallet { DefaultBonds, >; + #[pallet::storage] // --- Storage for migration run status + pub type HasMigrationRun = StorageMap<_, Identity, Vec, bool, ValueQuery>; + /// ================== /// ==== Genesis ===== /// ================== @@ -1419,7 +1422,9 @@ pub mod pallet { // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. .saturating_add(migration::migrate_populate_owned::()) // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. - .saturating_add(migration::migrate_populate_staking_hotkeys::()); + .saturating_add(migration::migrate_populate_staking_hotkeys::()) + // Fix total coldkey stake. + .saturating_add(migration::migrate_fix_total_coldkey_stake::()); weight } @@ -2062,17 +2067,17 @@ pub mod pallet { } /// The extrinsic for user to change its hotkey - #[pallet::call_index(70)] - #[pallet::weight((Weight::from_parts(1_940_000_000, 0) - .saturating_add(T::DbWeight::get().reads(272)) - .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - pub fn swap_hotkey( - origin: OriginFor, - hotkey: T::AccountId, - new_hotkey: T::AccountId, - ) -> DispatchResultWithPostInfo { - Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) - } + ///#[pallet::call_index(70)] + ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) + ///.saturating_add(T::DbWeight::get().reads(272)) + ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + ///pub fn swap_hotkey( + /// origin: OriginFor, + /// hotkey: T::AccountId, + /// new_hotkey: T::AccountId, + ///) -> DispatchResultWithPostInfo { + /// Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) + ///} /// The extrinsic for user to change the coldkey associated with their account. /// @@ -2099,7 +2104,6 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { Self::do_swap_coldkey(origin, &new_coldkey) } - /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. /// /// # Arguments @@ -2115,6 +2119,7 @@ pub mod pallet { /// # Weight /// /// Weight is calculated based on the number of database reads and writes. + #[cfg(test)] #[pallet::call_index(72)] #[pallet::weight((Weight::from_parts(21_000_000, 0) .saturating_add(T::DbWeight::get().reads(3)) @@ -2254,6 +2259,17 @@ pub mod pallet { pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { Self::user_remove_network(origin, netuid) } + + /// Sets values for liquid alpha + #[pallet::call_index(64)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_hotfix_swap_coldkey_delegates( + _origin: OriginFor, + _old_coldkey: T::AccountId, + _new_coldkey: T::AccountId, + ) -> DispatchResult { + Ok(()) + } } // ---- Subtensor helper functions. diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 35b3c4260..866ff08fd 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -1,4 +1,5 @@ use super::*; +use alloc::string::String; use frame_support::traits::DefensiveResult; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, @@ -24,6 +25,92 @@ pub mod deprecated_loaded_emission_format { StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; } +/// Migrates and fixes the total coldkey stake. +/// +/// This function iterates through all staking hotkeys, calculates the total stake for each coldkey, +/// and updates the `TotalColdkeyStake` storage accordingly. The migration is only performed if the +/// on-chain storage version is 6. +/// +/// # Returns +/// The weight of the migration process. +pub fn do_migrate_fix_total_coldkey_stake() -> Weight { + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Clear everything from the map first, no limit (u32::MAX) + let removal_results = TotalColdkeyStake::::clear(u32::MAX, None); + // 1 read/write per removal + let entries_removed: u64 = removal_results.backend.into(); + weight = + weight.saturating_add(T::DbWeight::get().reads_writes(entries_removed, entries_removed)); + + // Iterate through all staking hotkeys. + for (coldkey, hotkey_vec) in StakingHotkeys::::iter() { + // Init the zero value. + let mut coldkey_stake_sum: u64 = 0; + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // Calculate the total stake for the current coldkey. + for hotkey in hotkey_vec { + // Cant fail on retrieval. + coldkey_stake_sum = + coldkey_stake_sum.saturating_add(Stake::::get(hotkey, coldkey.clone())); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + // Update the `TotalColdkeyStake` storage with the calculated stake sum. + // Cant fail on insert. + TotalColdkeyStake::::insert(coldkey.clone(), coldkey_stake_sum); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + weight +} + +/// Migrates and fixes the total coldkey stake. +/// +/// This function checks if the migration has already run, and if not, it performs the migration +/// to fix the total coldkey stake. It also marks the migration as completed after running. +/// +/// # Returns +/// The weight of the migration process. +pub fn migrate_fix_total_coldkey_stake() -> Weight { + let migration_name = b"fix_total_coldkey_stake_v7".to_vec(); + + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Check if the migration has already run + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + migration_name + ); + return Weight::zero(); + } + + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // Run the migration + weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); + + // Mark the migration as completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Set the storage version to 7 + StorageVersion::new(7).put::>(); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed. Storage version set to 7.", + String::from_utf8_lossy(&migration_name) + ); + + // Return the migration weight. + weight +} /// Performs migration to update the total issuance based on the sum of stakes and total balances. /// This migration is applicable only if the current storage version is 5, after which it updates the storage version to 6. /// diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 24d67aeb4..8e4ca5cc9 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -31,6 +31,7 @@ impl Pallet { new_hotkey: &T::AccountId, ) -> DispatchResultWithPostInfo { let coldkey = ensure_signed(origin)?; + ensure!( !Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration @@ -60,6 +61,16 @@ impl Pallet { T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); + let swap_cost = Self::get_key_swap_cost(); + log::debug!("Swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); @@ -74,6 +85,7 @@ impl Pallet { Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); @@ -140,6 +152,20 @@ impl Pallet { Error::::ColdKeyAlreadyAssociated ); + // Calculate and charge the swap fee + let swap_cost = Self::get_key_swap_cost(); + log::debug!("Coldkey swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + let actual_burn_amount = + Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + // Actually do the swap. weight = weight.saturating_add( Self::perform_swap_coldkey(&old_coldkey, new_coldkey) @@ -189,15 +215,15 @@ impl Pallet { /// /// This function calculates the remaining arbitration period by subtracting the current block number /// from the arbitration block number of the coldkey. - // pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { - // let current_block: u64 = Self::get_current_block_as_u64(); - // let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); - // if arbitration_block > current_block { - // arbitration_block.saturating_sub(current_block) - // } else { - // 0 - // } - // } + pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { + let current_block: u64 = Self::get_current_block_as_u64(); + let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); + if arbitration_block > current_block { + arbitration_block.saturating_sub(current_block) + } else { + 0 + } + } pub fn meets_min_allowed_coldkey_balance(coldkey: &T::AccountId) -> bool { let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); @@ -825,7 +851,8 @@ impl Pallet { log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); if stake > 0 { // Insert the stake for the hotkey and new coldkey - Stake::::insert(hotkey, new_coldkey, stake); + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); total_transferred_stake = total_transferred_stake.saturating_add(stake); // Update the owner of the hotkey to the new coldkey @@ -835,6 +862,52 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } } + log::info!( + "Starting transfer of delegated stakes for old coldkey: {:?}", + old_coldkey + ); + + for staking_hotkey in StakingHotkeys::::get(old_coldkey) { + log::info!("Processing staking hotkey: {:?}", staking_hotkey); + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + log::info!( + "Transferring delegated stake for hotkey {:?}: {}", + hotkey, + stake + ); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + log::info!( + "Updated stake for hotkey {:?} under new coldkey {:?}: {}", + hotkey, + new_coldkey, + stake.saturating_add(old_stake) + ); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + } + } else { + log::info!( + "No stake found for staking hotkey {:?} under old coldkey {:?}", + staking_hotkey, + old_coldkey + ); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + + log::info!( + "Completed transfer of delegated stakes for old coldkey: {:?}", + old_coldkey + ); // Log the total transferred stake log::info!("Total transferred stake: {}", total_transferred_stake); @@ -862,13 +935,30 @@ impl Pallet { } // Update the list of owned hotkeys for both old and new coldkeys + + let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + for hotkey in old_owned_hotkeys { + if !new_owned_hotkeys.contains(&hotkey) { + new_owned_hotkeys.push(hotkey); + } + } + + OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); OwnedHotkeys::::remove(old_coldkey); - OwnedHotkeys::::insert(new_coldkey, old_owned_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Update the staking hotkeys for both old and new coldkeys - let staking_hotkeys: Vec = StakingHotkeys::::take(old_coldkey); - StakingHotkeys::::insert(new_coldkey, staking_hotkeys); + let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); + + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + for hotkey in staking_hotkeys { + if !existing_staking_hotkeys.contains(&hotkey) { + existing_staking_hotkeys.push(hotkey); + } + } + + StakingHotkeys::::remove(old_coldkey); + StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); // Log the total stake of old and new coldkeys after the swap @@ -881,6 +971,7 @@ impl Pallet { TotalColdkeyStake::::get(new_coldkey) ); } + /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. /// /// # Arguments @@ -948,4 +1039,17 @@ impl Pallet { } weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } + + pub fn swap_senate_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if T::SenateMembers::is_member(old_hotkey) { + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + Ok(()) + } } diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 94f18dfbf..c61133e94 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -671,8 +671,8 @@ impl Pallet { NominatorMinRequiredStake::::put(min_stake); } - pub fn get_hotkey_swap_cost() -> u64 { - T::HotkeySwapCost::get() + pub fn get_key_swap_cost() -> u64 { + T::KeySwapCost::get() } pub fn get_alpha_values(netuid: u16) -> (u16, u16) { diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index c921124f4..d47155862 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -1,9 +1,10 @@ #![allow(clippy::unwrap_used)] mod mock; -use frame_support::assert_ok; +use frame_support::{assert_ok, weights::Weight}; use frame_system::Config; use mock::*; +use pallet_subtensor::*; use sp_core::U256; #[test] @@ -276,3 +277,148 @@ fn test_migration_delete_subnet_21() { assert!(!SubtensorModule::if_subnet_exist(21)); }) } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 0); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + + let second_weight = run_migration_and_check(migration_name); + assert_eq!(second_weight, Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_value_already_in_total --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 100000000); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_no_entry --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_no_entry() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 100000000); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 0); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 100000000); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 20000); + }) +} + +// New test to check if migration runs only once +#[test] +fn test_migrate_fix_total_coldkey_stake_runs_once() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 0); + StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); + Stake::::insert(U256::from(1), U256::from(0), 10000); + Stake::::insert(U256::from(2), U256::from(0), 10000); + Stake::::insert(U256::from(3), U256::from(0), 10000); + + // First run + let first_weight = run_migration_and_check(migration_name); + assert!(first_weight != Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + + // Second run + let second_weight = run_migration_and_check(migration_name); + assert_eq!(second_weight, Weight::zero()); + assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_starts_with_value_no_stake_map_entries --exact --nocapture +#[test] +fn test_migrate_fix_total_coldkey_stake_starts_with_value_no_stake_map_entries() { + new_test_ext(1).execute_with(|| { + let migration_name = "fix_total_coldkey_stake_v7"; + let coldkey = U256::from(0); + TotalColdkeyStake::::insert(coldkey, 123_456_789); + + // Notably, coldkey has no stake map or staking_hotkeys map entries + + let weight = run_migration_and_check(migration_name); + assert!(weight != Weight::zero()); + // Therefore 0 + assert_eq!(TotalColdkeyStake::::get(coldkey), 0); + }) +} + +fn run_migration_and_check(migration_name: &'static str) -> frame_support::weights::Weight { + // Execute the migration and store its weight + let weight: frame_support::weights::Weight = + pallet_subtensor::migration::migrate_fix_total_coldkey_stake::(); + + // Check if the migration has been marked as completed + assert!(HasMigrationRun::::get( + migration_name.as_bytes().to_vec() + )); + + // Return the weight of the executed migration + weight +} diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 3aa6123e1..aa2c4ad30 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -165,7 +165,7 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 2; - pub const InitialHotkeySwapCost: u64 = 1_000_000_000; + pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -376,7 +376,7 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; - type HotkeySwapCost = InitialHotkeySwapCost; + type KeySwapCost = InitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 676d49a44..bd95ae3b1 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -1868,153 +1868,153 @@ fn test_registration_disabled() { }); } -#[ignore] -#[test] -fn test_hotkey_swap_ok() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(667); - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10_000_000_000); - - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); - - let new_hotkey = U256::from(1337); - assert_ok!(SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - new_hotkey - )); - assert_ne!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey_account_id), - coldkey_account_id - ); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), - coldkey_account_id - ); - }); -} - -#[ignore] -#[test] -fn test_hotkey_swap_not_owner() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(2); - let not_owner_coldkey = U256::from(3); - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); - - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); - - let new_hotkey = U256::from(4); - assert_err!( - SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(not_owner_coldkey), - hotkey_account_id, - new_hotkey - ), - Error::::NonAssociatedColdKey - ); - }); -} - -#[ignore] -#[test] -fn test_hotkey_swap_same_key() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(2); - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); +// #[ignore] +// #[test] +// fn test_hotkey_swap_ok() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(667); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10_000_000_000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); +// let new_hotkey = U256::from(1337); +// assert_ok!(SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// hotkey_account_id, +// new_hotkey +// )); +// assert_ne!( +// SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey_account_id), +// coldkey_account_id +// ); +// assert_eq!( +// SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), +// coldkey_account_id +// ); +// }); +// } - assert_err!( - SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - hotkey_account_id - ), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - }); -} +// #[ignore] +// #[test] +// fn test_hotkey_swap_not_owner() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(2); +// let not_owner_coldkey = U256::from(3); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); -#[ignore] -#[test] -fn test_hotkey_swap_registered_key() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let hotkey_account_id = U256::from(1); - let burn_cost = 1000; - let coldkey_account_id = U256::from(2); +// let new_hotkey = U256::from(4); +// assert_err!( +// SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(not_owner_coldkey), +// hotkey_account_id, +// new_hotkey +// ), +// Error::::NonAssociatedColdKey +// ); +// }); +// } - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); +// #[ignore] +// #[test] +// fn test_hotkey_swap_same_key() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(2); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 10000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); - // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 100_000_000_000); +// assert_err!( +// SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// hotkey_account_id, +// hotkey_account_id +// ), +// Error::::HotKeyAlreadyRegisteredInSubNet +// ); +// }); +// } - // Subscribe and check extrinsic output - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - hotkey_account_id - )); +// #[ignore] +// #[test] +// fn test_hotkey_swap_registered_key() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let tempo: u16 = 13; +// let hotkey_account_id = U256::from(1); +// let burn_cost = 1000; +// let coldkey_account_id = U256::from(2); + +// SubtensorModule::set_burn(netuid, burn_cost); +// add_network(netuid, tempo, 0); + +// // Give it some $$$ in his coldkey balance +// SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 100_000_000_000); + +// // Subscribe and check extrinsic output +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// hotkey_account_id +// )); - let new_hotkey = U256::from(3); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(coldkey_account_id), - netuid, - new_hotkey - )); +// let new_hotkey = U256::from(3); +// assert_ok!(SubtensorModule::burned_register( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// netuid, +// new_hotkey +// )); - assert_err!( - SubtensorModule::swap_hotkey( - <::RuntimeOrigin>::signed(coldkey_account_id), - hotkey_account_id, - new_hotkey - ), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - }); -} +// assert_err!( +// SubtensorModule::swap_hotkey( +// <::RuntimeOrigin>::signed(coldkey_account_id), +// hotkey_account_id, +// new_hotkey +// ), +// Error::::HotKeyAlreadyRegisteredInSubNet +// ); +// }); +// } diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 5db439e5b..cb2cac4ef 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -4719,3 +4719,35 @@ fn test_do_schedule_coldkey_swap_regular_user_passes_min_balance() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_emission_creates_staking_hotkeys_entry --exact --nocapture +#[test] +fn test_emission_creates_staking_hotkeys_entry() { + new_test_ext(1).execute_with(|| { + let hotkey0 = U256::from(1); + let hotkey1 = U256::from(2); + + let coldkey = U256::from(3); + + // Add to Owner map + Owner::::insert(hotkey0, coldkey); + Owner::::insert(hotkey1, coldkey); + OwnedHotkeys::::insert(coldkey, vec![hotkey0, hotkey1]); + + // Emit through hotkey + SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 1_000); + + // Verify StakingHotkeys has an entry + assert_eq!(StakingHotkeys::::get(coldkey).len(), 1); + assert!(StakingHotkeys::::get(coldkey).contains(&hotkey0)); + + // Try again with another emission on hotkey1 + SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 2_000); + + // Verify both hotkeys are now in the map + assert_eq!(StakingHotkeys::::get(coldkey).len(), 2); + let final_map = StakingHotkeys::::get(coldkey); + assert!(final_map.contains(&hotkey0)); + assert!(final_map.contains(&hotkey1)); + }) +} diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 90ebdfdcb..21c3a983a 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -2,8 +2,8 @@ use codec::Encode; use frame_support::weights::Weight; -use frame_support::{assert_err, assert_ok}; -use frame_system::Config; +use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_system::{Config, RawOrigin}; mod mock; use mock::*; use pallet_subtensor::*; @@ -167,6 +167,22 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::add_balance_to_coldkey_account(coldkey, swap_cost); } + // Add old_hotkeys[0] and old_hotkeys[1] to Senate + assert_ok!(SenateMembers::add_member( + RawOrigin::Root.into(), + old_hotkeys[0] + )); + assert_ok!(SenateMembers::add_member( + RawOrigin::Root.into(), + old_hotkeys[1] + )); + + // Verify initial Senate membership + assert!(Senate::is_member(&old_hotkeys[0])); + assert!(Senate::is_member(&old_hotkeys[1])); + assert!(!Senate::is_member(&new_hotkeys[0])); + assert!(!Senate::is_member(&new_hotkeys[1])); + // Perform the swaps for only two hotkeys assert_ok!(SubtensorModule::do_swap_hotkey( <::RuntimeOrigin>::signed(coldkeys[0]), @@ -268,6 +284,10 @@ fn test_do_swap_hotkey_ok_robust() { assert_eq!(Keys::::get(netuid, uid), new_hotkeys[i]); } } + + // Verify Senate membership swap + assert!(!Senate::is_member(&old_hotkeys[i])); + assert!(Senate::is_member(&new_hotkeys[i])); } else { // Ensure other hotkeys remain unchanged assert_eq!( @@ -278,6 +298,10 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkeys[i]), coldkeys[i] ); + + // Verify Senate membership remains unchanged for other hotkeys + assert!(!Senate::is_member(&old_hotkeys[i])); + assert!(!Senate::is_member(&new_hotkeys[i])); } } } @@ -1059,7 +1083,8 @@ fn test_do_swap_coldkey_success() { let netuid = 1u16; let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; - let free_balance_old = 12345u64 + MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; + let swap_cost = SubtensorModule::get_key_swap_cost(); + let free_balance_old = 12345u64 + swap_cost; // Setup initial state add_network(netuid, 13, 0); @@ -1158,7 +1183,7 @@ fn test_do_swap_coldkey_success() { // Verify balance transfer assert_eq!( SubtensorModule::get_coldkey_balance(&new_coldkey), - free_balance_old + free_balance_old - swap_cost ); assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); @@ -1180,6 +1205,7 @@ fn test_do_swap_coldkey_success() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_swap_stake_for_coldkey --exact --nocaptur #[test] fn test_swap_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -1189,13 +1215,22 @@ fn test_swap_stake_for_coldkey() { let hotkey2 = U256::from(4); let stake_amount1 = 1000u64; let stake_amount2 = 2000u64; + let stake_amount3 = 3000u64; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); // Setup initial state OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); Stake::::insert(hotkey1, old_coldkey, stake_amount1); Stake::::insert(hotkey2, old_coldkey, stake_amount2); + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1); + assert_eq!(Stake::::get(hotkey1, old_coldkey), stake_amount1); + + // Insert existing for same hotkey1 + Stake::::insert(hotkey1, new_coldkey, stake_amount3); + StakingHotkeys::::insert(new_coldkey, vec![hotkey1]); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); TotalHotkeyStake::::insert(hotkey2, stake_amount2); TotalColdkeyStake::::insert(old_coldkey, total_stake); @@ -1211,6 +1246,12 @@ fn test_swap_stake_for_coldkey() { // Perform the swap SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + // Verify stake is additive, not replaced + assert_eq!( + Stake::::get(hotkey1, new_coldkey), + stake_amount1 + stake_amount3 + ); + // Verify ownership transfer assert_eq!( SubtensorModule::get_owned_hotkeys(&new_coldkey), @@ -1218,6 +1259,104 @@ fn test_swap_stake_for_coldkey() { ); assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); + // Verify stake transfer + assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); + assert_eq!(Stake::::get(hotkey1, old_coldkey), 0); + assert_eq!(Stake::::get(hotkey2, old_coldkey), 0); + + // Verify TotalColdkeyStake + assert_eq!(TotalColdkeyStake::::get(new_coldkey), total_stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + + // Verify TotalHotkeyStake remains unchanged + assert_eq!(TotalHotkeyStake::::get(hotkey1), stake_amount1); + assert_eq!(TotalHotkeyStake::::get(hotkey2), stake_amount2); + + // Verify total stake and issuance remain unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + initial_total_stake, + "Total stake changed unexpectedly" + ); + assert_eq!( + SubtensorModule::get_total_issuance(), + initial_total_issuance, + "Total issuance changed unexpectedly" + ); + }); +} + +#[test] +fn test_swap_staking_hotkeys_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let total_stake = stake_amount1 + stake_amount2; + let mut weight = Weight::zero(); + + // Setup initial state + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + Stake::::insert(hotkey1, old_coldkey, stake_amount1); + Stake::::insert(hotkey2, old_coldkey, stake_amount2); + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); + TotalHotkeyStake::::insert(hotkey2, stake_amount2); + TotalColdkeyStake::::insert(old_coldkey, total_stake); + + // Set up total issuance + TotalIssuance::::put(total_stake); + TotalStake::::put(total_stake); + + // Perform the swap + SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify StakingHotkeys transfer + assert_eq!( + StakingHotkeys::::get(new_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); + }); +} + +#[test] +fn test_swap_delegated_stake_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let total_stake = stake_amount1 + stake_amount2; + let mut weight = Weight::zero(); + + // Notice hotkey1 and hotkey2 are not in OwnedHotkeys + // coldkey therefore delegates stake to them + + // Setup initial state + StakingHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); + Stake::::insert(hotkey1, old_coldkey, stake_amount1); + Stake::::insert(hotkey2, old_coldkey, stake_amount2); + TotalHotkeyStake::::insert(hotkey1, stake_amount1); + TotalHotkeyStake::::insert(hotkey2, stake_amount2); + TotalColdkeyStake::::insert(old_coldkey, total_stake); + + // Set up total issuance + TotalIssuance::::put(total_stake); + TotalStake::::put(total_stake); + + // Record initial values + let initial_total_issuance = SubtensorModule::get_total_issuance(); + let initial_total_stake = SubtensorModule::get_total_stake(); + + // Perform the swap + SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + // Verify stake transfer assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); assert_eq!(Stake::::get(hotkey2, new_coldkey), stake_amount2); @@ -1332,6 +1471,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { let hotkey = U256::from(3); let netuid = 1u16; let stake_amount: u64 = 1000u64; + let swap_cost = SubtensorModule::get_key_swap_cost(); // Setup initial state add_network(netuid, 13, 0); @@ -1340,7 +1480,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Set TotalNetworks because swap relies on it pallet_subtensor::TotalNetworks::::set(1); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); SubnetOwner::::insert(netuid, old_coldkey); // Populate OwnedHotkeys map @@ -1666,24 +1806,84 @@ fn test_coldkey_swap_total() { }); } -// #[test] -// fn test_coldkey_arbitrated_sw() { -// new_test_ext(1).execute_with(|| { -// let coldkey = U256::from(1); -// let hotkey = U256::from(2); -// let netuid = 1u16; - -// // Setup initial state -// add_network(netuid, 13, 0); -// register_ok_neuron(netuid, hotkey, coldkey, 0); - -// // Check if coldkey has associated hotkeys -// assert!(SubtensorModule::coldkey_has_associated_hotkeys(&coldkey)); - -// // Check for a coldkey without associated hotkeys -// let unassociated_coldkey = U256::from(3); -// assert!(!SubtensorModule::coldkey_has_associated_hotkeys( -// &unassociated_coldkey -// )); -// }); -// } +#[test] +fn test_swap_senate_member() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let non_member_hotkey = U256::from(3); + let mut weight = Weight::zero(); + + // Setup: Add old_hotkey as a Senate member + assert_ok!(SenateMembers::add_member( + RawOrigin::Root.into(), + old_hotkey + )); + + // Test 1: Successful swap + assert_ok!(SubtensorModule::swap_senate_member( + &old_hotkey, + &new_hotkey, + &mut weight + )); + assert!(Senate::is_member(&new_hotkey)); + assert!(!Senate::is_member(&old_hotkey)); + + // Verify weight update + let expected_weight = ::DbWeight::get().reads_writes(2, 2); + assert_eq!(weight, expected_weight); + + // Reset weight for next test + weight = Weight::zero(); + + // Test 2: Swap with non-member (should not change anything) + assert_ok!(SubtensorModule::swap_senate_member( + &non_member_hotkey, + &new_hotkey, + &mut weight + )); + assert!(Senate::is_member(&new_hotkey)); + assert!(!Senate::is_member(&non_member_hotkey)); + + // Verify weight update (should only have read operations) + let expected_weight = ::DbWeight::get().reads(1); + assert_eq!(weight, expected_weight); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_delegations --exact --nocapture +#[test] +fn test_coldkey_delegations() { + new_test_ext(1).execute_with(|| { + let new_coldkey = U256::from(0); + let owner = U256::from(1); + let coldkey = U256::from(4); + let delegate = U256::from(2); + let netuid = 1u16; + add_network(netuid, 13, 0); + register_ok_neuron(netuid, delegate, owner, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + assert_ok!(SubtensorModule::do_become_delegate( + <::RuntimeOrigin>::signed(owner), + delegate, + u16::MAX / 10 + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey), + delegate, + 100 + )); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey + )); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate), 100); + assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 0); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + 100 + ); + assert_eq!(Stake::::get(delegate, new_coldkey), 100); + assert_eq!(Stake::::get(delegate, coldkey), 0); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index de8be6e61..287ded7c5 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 194, + spec_version: 195, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -312,8 +312,7 @@ impl Contains for SafeModeWhitelistedCalls { | RuntimeCall::SafeMode(_) | RuntimeCall::Timestamp(_) | RuntimeCall::SubtensorModule( - pallet_subtensor::Call::schedule_coldkey_swap { .. } - | pallet_subtensor::Call::set_weights { .. } + pallet_subtensor::Call::set_weights { .. } | pallet_subtensor::Call::set_root_weights { .. } | pallet_subtensor::Call::serve_axon { .. } ) @@ -877,7 +876,7 @@ parameter_types! { pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; pub const SubtensorInitialTargetStakesPerInterval: u16 = 1; - pub const SubtensorInitialHotkeySwapCost: u64 = 1_000_000_000; + pub const SubtensorInitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn @@ -933,7 +932,7 @@ impl pallet_subtensor::Config for Runtime { type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval; - type HotkeySwapCost = SubtensorInitialHotkeySwapCost; + type KeySwapCost = SubtensorInitialKeySwapCost; type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; From 1c502952ec932ffab75fb28b3ad800becc91c0d2 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 22 Jul 2024 11:19:43 -0400 Subject: [PATCH 183/185] Feat/devnet companion/check metadata hash extension (#649) * draft: hotkey swap for senate * feature-gate hash * use 1.10.0-rc2 * add feature * feat: remove schedule coldkey swap * chore: fix tests * fixes for network resumption * chore: bump spec * fix: add back arbitration check * fix: hotkey * fix: coldkey arb swap hotkey * swap delegate stake also * add test for this * swap over stakinghotkeys map * check map first * add staking hotkeys test * no take * keep old stake * add check to test * fix some tests * fix delegate test * update staking hotekys maps * init * comment out * add admin swap * fix swap * .. * hotkey staking maps fix * remove staking hotkeys * remove commented code * cargo fmt * cargo fix --workspace * bump spec_version to 165 * swap hotkey benchmark removed * add migration and tests for total coldkey hotfix * adds new test for missing hotkey value * bump migration values * fmt * lock file update * add production just target * fmt * clippy * benchmarking fix * feat: try runtime passing * chore: remove commented code * chore: make logs human readable * chore: remove comments * Update pallets/subtensor/src/lib.rs Co-authored-by: Sam Johnson * fmt * use rc3 with new fix polkadot-sdk/pull/4117 * incl lock * bump CI --------- Co-authored-by: Samuel Dare Co-authored-by: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Co-authored-by: const Co-authored-by: Sam Johnson Co-authored-by: Unconst <32490803+unconst@users.noreply.github.com> --- Cargo.lock | 485 ++++++++++++++++++++++----------------- Cargo.toml | 139 +++++------ justfile | 6 +- node/Cargo.toml | 1 + node/src/benchmarking.rs | 2 + runtime/Cargo.toml | 3 + runtime/build.rs | 11 +- runtime/src/lib.rs | 1 + scripts/build.sh | 2 +- 9 files changed, 370 insertions(+), 280 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 868e85b89..4a50f8a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2308,7 +2308,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", ] @@ -2331,7 +2331,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-support-procedural", @@ -2347,16 +2347,16 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "static_assertions", ] [[package]] name = "frame-benchmarking-cli" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "array-bytes 6.2.3", @@ -2388,15 +2388,15 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-inherents", "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "thousands", ] @@ -2404,7 +2404,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "aquamarine 0.3.3", "frame-support", @@ -2416,8 +2416,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -2432,10 +2432,25 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-metadata-hash-extension" +version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +dependencies = [ + "array-bytes 6.2.3", + "docify", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", +] + [[package]] name = "frame-support" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "aquamarine 0.5.0", "array-bytes 6.2.3", @@ -2458,7 +2473,7 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-crypto-hashing-proc-macro", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -2466,8 +2481,8 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", "static_assertions", "tt-call", @@ -2476,7 +2491,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "cfg-expr", @@ -2495,7 +2510,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 3.1.0", @@ -2507,7 +2522,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro2", "quote", @@ -2517,7 +2532,7 @@ dependencies = [ [[package]] name = "frame-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "cfg-if", "docify", @@ -2529,7 +2544,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version", "sp-weights", ] @@ -2537,7 +2552,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -2546,13 +2561,13 @@ dependencies = [ "scale-info", "sp-core", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "frame-system-rpc-runtime-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "sp-api", @@ -2561,13 +2576,13 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "parity-scale-codec", "sp-api", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -4334,6 +4349,20 @@ dependencies = [ "hash-db", ] +[[package]] +name = "merkleized-metadata" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f313fcff1d2a4bcaa2deeaa00bf7530d77d5f7bd0467a117dde2e29a75a7a17a" +dependencies = [ + "array-bytes 6.2.3", + "blake3", + "frame-metadata", + "parity-scale-codec", + "scale-decode", + "scale-info", +] + [[package]] name = "merlin" version = "3.0.0" @@ -4644,6 +4673,7 @@ dependencies = [ "clap", "frame-benchmarking", "frame-benchmarking-cli", + "frame-metadata-hash-extension", "frame-system", "futures", "jsonrpsee", @@ -4699,6 +4729,7 @@ dependencies = [ "frame-benchmarking", "frame-executive", "frame-metadata", + "frame-metadata-hash-extension", "frame-support", "frame-system", "frame-system-benchmarking", @@ -4739,9 +4770,9 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", @@ -4952,7 +4983,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", "substrate-fixed", "subtensor-macros", @@ -4961,7 +4992,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -4972,13 +5003,13 @@ dependencies = [ "sp-application-crypto", "sp-consensus-aura", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-authorship" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -4986,13 +5017,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-balances" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5002,7 +5033,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -5018,7 +5049,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "subtensor-macros", ] @@ -5036,14 +5067,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "subtensor-macros", ] [[package]] name = "pallet-grandpa" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5060,13 +5091,13 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5074,13 +5105,13 @@ dependencies = [ "safe-mix", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-membership" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5091,13 +5122,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-multisig" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5107,13 +5138,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-preimage" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5124,13 +5155,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-proxy" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5139,7 +5170,7 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -5155,14 +5186,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "subtensor-macros", ] [[package]] name = "pallet-safe-mode" version = "9.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5175,13 +5206,13 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-scheduler" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5192,14 +5223,14 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", ] [[package]] name = "pallet-session" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5214,7 +5245,7 @@ dependencies = [ "sp-session", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", ] @@ -5246,8 +5277,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version", "substrate-fixed", "subtensor-macros", @@ -5256,7 +5287,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5266,13 +5297,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-timestamp" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5284,15 +5315,15 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-timestamp", ] [[package]] name = "pallet-transaction-payment" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5302,13 +5333,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-transaction-payment-rpc" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -5324,7 +5355,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -5336,7 +5367,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5346,7 +5377,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -6684,18 +6715,18 @@ dependencies = [ [[package]] name = "sc-allocator" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "sp-core", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", ] [[package]] name = "sc-basic-authorship" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "futures-timer", @@ -6717,7 +6748,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "sp-api", @@ -6732,7 +6763,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "docify", @@ -6758,7 +6789,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -6769,7 +6800,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "chrono", @@ -6810,7 +6841,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "fnv", "futures", @@ -6825,11 +6856,11 @@ dependencies = [ "sp-consensus", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-runtime", "sp-state-machine", "sp-statement-store", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "substrate-prometheus-endpoint", ] @@ -6837,7 +6868,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hash-db", "kvdb", @@ -6863,7 +6894,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -6888,7 +6919,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -6917,7 +6948,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ahash 0.8.11", "array-bytes 6.2.3", @@ -6960,7 +6991,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa-rpc" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "finality-grandpa", "futures", @@ -6980,7 +7011,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -7003,7 +7034,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", @@ -7013,25 +7044,25 @@ dependencies = [ "schnellru", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-io", "sp-panic-handler", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "sp-version", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "tracing", ] [[package]] name = "sc-executor-common" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "polkavm", "sc-allocator", "sp-maybe-compressed-blob", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "wasm-instrument", ] @@ -7039,18 +7070,18 @@ dependencies = [ [[package]] name = "sc-executor-polkavm" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "polkavm", "sc-executor-common", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sc-executor-wasmtime" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "anyhow", "cfg-if", @@ -7060,15 +7091,15 @@ dependencies = [ "rustix 0.36.17", "sc-allocator", "sc-executor-common", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "wasmtime", ] [[package]] name = "sc-informant" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ansi_term", "futures", @@ -7085,7 +7116,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "25.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "parking_lot 0.12.3", @@ -7099,7 +7130,7 @@ dependencies = [ [[package]] name = "sc-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 4.2.0", "arrayvec", @@ -7128,7 +7159,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7171,7 +7202,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-channel", "cid", @@ -7191,7 +7222,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -7208,7 +7239,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ahash 0.8.11", "futures", @@ -7227,7 +7258,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7248,7 +7279,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7284,7 +7315,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7303,7 +7334,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "bytes", @@ -7326,7 +7357,7 @@ dependencies = [ "sc-utils", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-keystore", "sp-offchain", "sp-runtime", @@ -7337,7 +7368,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -7346,7 +7377,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "jsonrpsee", @@ -7378,7 +7409,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -7398,7 +7429,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "governor", @@ -7416,7 +7447,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7447,7 +7478,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "directories", @@ -7489,12 +7520,12 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", @@ -7511,7 +7542,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.30.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "parity-scale-codec", @@ -7522,7 +7553,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "derive_more", "futures", @@ -7537,13 +7568,13 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sc-telemetry" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "chrono", "futures", @@ -7562,7 +7593,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ansi_term", "chrono", @@ -7582,7 +7613,7 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "tracing", "tracing-log 0.1.4", @@ -7592,7 +7623,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -7603,7 +7634,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -7621,7 +7652,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-transaction-pool", "substrate-prometheus-endpoint", "thiserror", @@ -7630,7 +7661,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -7646,7 +7677,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-channel", "futures", @@ -7658,6 +7689,29 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "scale-bits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" +dependencies = [ + "parity-scale-codec", + "scale-type-resolver", +] + +[[package]] +name = "scale-decode" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" +dependencies = [ + "derive_more", + "parity-scale-codec", + "scale-bits", + "scale-type-resolver", + "smallvec", +] + [[package]] name = "scale-info" version = "2.11.3" @@ -7684,6 +7738,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scale-type-resolver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" + [[package]] name = "schannel" version = "0.1.23" @@ -8129,7 +8189,7 @@ dependencies = [ [[package]] name = "sp-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hash-db", "log", @@ -8137,12 +8197,12 @@ dependencies = [ "scale-info", "sp-api-proc-macro", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-metadata-ir", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "sp-version", "thiserror", @@ -8151,7 +8211,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "blake2 0.10.6", @@ -8165,20 +8225,20 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", "serde", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sp-arithmetic" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "integer-sqrt", @@ -8186,7 +8246,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "static_assertions", ] @@ -8211,7 +8271,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-api", "sp-inherents", @@ -8221,7 +8281,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "log", @@ -8239,7 +8299,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -8254,7 +8314,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "parity-scale-codec", @@ -8270,7 +8330,7 @@ dependencies = [ [[package]] name = "sp-consensus-grandpa" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "finality-grandpa", "log", @@ -8287,7 +8347,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", @@ -8298,7 +8358,7 @@ dependencies = [ [[package]] name = "sp-core" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "bandersnatch_vrfs", @@ -8329,11 +8389,11 @@ dependencies = [ "secrecy", "serde", "sp-crypto-hashing", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "ss58-registry", "substrate-bip39", "thiserror", @@ -8365,7 +8425,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "blake2b_simd", "byteorder", @@ -8378,7 +8438,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "quote", "sp-crypto-hashing", @@ -8388,7 +8448,7 @@ dependencies = [ [[package]] name = "sp-database" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "kvdb", "parking_lot 0.12.3", @@ -8397,7 +8457,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro2", "quote", @@ -8417,11 +8477,11 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -8437,7 +8497,7 @@ dependencies = [ [[package]] name = "sp-genesis-builder" version = "0.7.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "serde_json", "sp-api", @@ -8447,7 +8507,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -8460,7 +8520,7 @@ dependencies = [ [[package]] name = "sp-io" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "bytes", "ed25519-dalek", @@ -8472,12 +8532,12 @@ dependencies = [ "secp256k1", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-keystore", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "tracing", "tracing-core", @@ -8486,7 +8546,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "31.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-core", "sp-runtime", @@ -8496,18 +8556,18 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "thiserror", "zstd 0.12.4", @@ -8516,7 +8576,7 @@ dependencies = [ [[package]] name = "sp-metadata-ir" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-metadata", "parity-scale-codec", @@ -8526,7 +8586,7 @@ dependencies = [ [[package]] name = "sp-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", @@ -8537,7 +8597,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-api", "sp-core", @@ -8547,7 +8607,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "backtrace", "lazy_static", @@ -8557,7 +8617,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "rustc-hash", "serde", @@ -8567,7 +8627,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "31.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "either", @@ -8584,26 +8644,26 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", ] [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive", "primitive-types", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "static_assertions", ] @@ -8629,7 +8689,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "expander", @@ -8655,7 +8715,7 @@ dependencies = [ [[package]] name = "sp-session" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", @@ -8669,7 +8729,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -8682,7 +8742,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hash-db", "log", @@ -8691,7 +8751,7 @@ dependencies = [ "rand", "smallvec", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-panic-handler", "sp-trie", "thiserror", @@ -8702,7 +8762,7 @@ dependencies = [ [[package]] name = "sp-statement-store" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "aes-gcm", "curve25519-dalek 4.1.3", @@ -8716,9 +8776,9 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "x25519-dalek 2.0.1", ] @@ -8726,7 +8786,7 @@ dependencies = [ [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" [[package]] name = "sp-std" @@ -8736,13 +8796,13 @@ source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06f [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "impl-serde", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -8760,7 +8820,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "parity-scale-codec", @@ -8772,7 +8832,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "tracing", @@ -8794,7 +8854,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-api", "sp-runtime", @@ -8803,7 +8863,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "parity-scale-codec", @@ -8817,7 +8877,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ahash 0.8.11", "hash-db", @@ -8830,7 +8890,7 @@ dependencies = [ "scale-info", "schnellru", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "tracing", "trie-db", @@ -8840,7 +8900,7 @@ dependencies = [ [[package]] name = "sp-version" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8849,7 +8909,7 @@ dependencies = [ "serde", "sp-crypto-hashing-proc-macro", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version-proc-macro", "thiserror", ] @@ -8857,7 +8917,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -8868,7 +8928,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -8890,7 +8950,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -8898,7 +8958,7 @@ dependencies = [ "serde", "smallvec", "sp-arithmetic", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -9037,7 +9097,7 @@ dependencies = [ [[package]] name = "substrate-bip39" version = "0.4.7" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hmac 0.12.1", "pbkdf2", @@ -9049,7 +9109,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" [[package]] name = "substrate-fixed" @@ -9065,7 +9125,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -9084,7 +9144,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hyper", "log", @@ -9096,15 +9156,24 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ + "array-bytes 6.2.3", "build-helper", "cargo_metadata", "console", "filetime", + "frame-metadata", + "merkleized-metadata", + "parity-scale-codec", "parity-wasm", "polkavm-linker", + "sc-executor", + "sp-core", + "sp-io", "sp-maybe-compressed-blob", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-version", "strum 0.26.2", "tempfile", "toml 0.8.14", diff --git a/Cargo.toml b/Cargo.toml index 8d9eff122..4a7565a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,80 +39,81 @@ litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } subtensor-macros = { path = "support/macros" } -frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" , default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } substrate-fixed = { git = "https://github.com/encointer/substrate-fixed.git", tag = "v0.5.9" } -substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } frame-metadata = "16" [profile.release] diff --git a/justfile b/justfile index e33fdf685..f99f3913a 100644 --- a/justfile +++ b/justfile @@ -47,4 +47,8 @@ lint: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." just clippy-fix @echo "Running cargo clippy..." - just clippy \ No newline at end of file + just clippy + +production: + @echo "Running cargo build with metadata-hash generation..." + cargo +{{RUSTV}} build --profile production --features="runtime-benchmarks metadata-hash" diff --git a/node/Cargo.toml b/node/Cargo.toml index 7fc6eff48..0e1a418a3 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -54,6 +54,7 @@ sp-io = { workspace = true } sp-timestamp = { workspace = true } sp-inherents = { workspace = true } sp-keyring = { workspace = true } +frame-metadata-hash-extension = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-commitments = { path = "../pallets/commitments" } diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index ba176e15f..cf48df62f 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -136,6 +136,7 @@ pub fn create_benchmark_extrinsic( pallet_transaction_payment::ChargeTransactionPayment::::from(0), pallet_subtensor::SubtensorSignedExtension::::new(), pallet_commitments::CommitmentsSignedExtension::::new(), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -152,6 +153,7 @@ pub fn create_benchmark_extrinsic( (), (), (), + None, ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index fcb02a24c..042d0337c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -41,6 +41,7 @@ pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-utility = { workspace = true } frame-executive = { workspace = true } +frame-metadata-hash-extension = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } @@ -111,6 +112,7 @@ std = [ "codec/std", "scale-info/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", @@ -204,3 +206,4 @@ try-runtime = [ "pallet-commitments/try-runtime", "pallet-registry/try-runtime" ] +metadata-hash = ["substrate-wasm-builder/metadata-hash"] diff --git a/runtime/build.rs b/runtime/build.rs index 8f021e838..c0fa0405b 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -1,5 +1,5 @@ fn main() { - #[cfg(feature = "std")] + #[cfg(all(feature = "std", not(feature = "metadata-hash")))] { substrate_wasm_builder::WasmBuilder::new() .with_current_project() @@ -7,4 +7,13 @@ fn main() { .import_memory() .build(); } + #[cfg(all(feature = "std", feature = "metadata-hash"))] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .enable_metadata_hash("TAO", 9) + .build(); + } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 287ded7c5..6547a3f33 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1282,6 +1282,7 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, pallet_subtensor::SubtensorSignedExtension, pallet_commitments::CommitmentsSignedExtension, + frame_metadata_hash_extension::CheckMetadataHash, ); type Migrations = pallet_grandpa::migrations::MigrateV4ToV5; diff --git a/scripts/build.sh b/scripts/build.sh index 548af664b..3f588a1cc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,2 +1,2 @@ -cargo build --profile production --features runtime-benchmarks +cargo build --profile production --features "runtime-benchmarks metadata-hash" From 2bb1557bc9ebc1f4bb7a118c2955660626072ded Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 23 Jul 2024 01:06:20 +0400 Subject: [PATCH 184/185] Revert "Merge branch 'devnet-ready' into main" This reverts commit c9202c9cbd46bdaee8d503cde8c0d1be5325d9a2, reversing changes made to a2b8d70b3ecdf4a9a8363155dcb0a8fb926b125e. --- Cargo.lock | 485 ++++++++++++---------------- Cargo.toml | 139 ++++---- justfile | 6 +- node/Cargo.toml | 1 - node/src/benchmarking.rs | 2 - pallets/subtensor/src/benchmarks.rs | 2 +- pallets/subtensor/src/epoch.rs | 2 + pallets/subtensor/src/lib.rs | 7 +- pallets/subtensor/src/math.rs | 85 ++--- pallets/subtensor/tests/mock.rs | 9 +- runtime/Cargo.toml | 3 - runtime/build.rs | 11 +- runtime/src/lib.rs | 1 - scripts/build.sh | 2 +- 14 files changed, 338 insertions(+), 417 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a50f8a12..868e85b89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2308,7 +2308,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", ] @@ -2331,7 +2331,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "frame-support-procedural", @@ -2347,16 +2347,16 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "static_assertions", ] [[package]] name = "frame-benchmarking-cli" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "Inflector", "array-bytes 6.2.3", @@ -2388,15 +2388,15 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-inherents", "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-trie", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "thiserror", "thousands", ] @@ -2404,7 +2404,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aquamarine 0.3.3", "frame-support", @@ -2416,8 +2416,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -2432,25 +2432,10 @@ dependencies = [ "serde", ] -[[package]] -name = "frame-metadata-hash-extension" -version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" -dependencies = [ - "array-bytes 6.2.3", - "docify", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-runtime", -] - [[package]] name = "frame-support" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aquamarine 0.5.0", "array-bytes 6.2.3", @@ -2473,7 +2458,7 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-crypto-hashing-proc-macro", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -2481,8 +2466,8 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-weights", "static_assertions", "tt-call", @@ -2491,7 +2476,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "Inflector", "cfg-expr", @@ -2510,7 +2495,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 3.1.0", @@ -2522,7 +2507,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "proc-macro2", "quote", @@ -2532,7 +2517,7 @@ dependencies = [ [[package]] name = "frame-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "cfg-if", "docify", @@ -2544,7 +2529,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-version", "sp-weights", ] @@ -2552,7 +2537,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -2561,13 +2546,13 @@ dependencies = [ "scale-info", "sp-core", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "frame-system-rpc-runtime-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "sp-api", @@ -2576,13 +2561,13 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "parity-scale-codec", "sp-api", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -4349,20 +4334,6 @@ dependencies = [ "hash-db", ] -[[package]] -name = "merkleized-metadata" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f313fcff1d2a4bcaa2deeaa00bf7530d77d5f7bd0467a117dde2e29a75a7a17a" -dependencies = [ - "array-bytes 6.2.3", - "blake3", - "frame-metadata", - "parity-scale-codec", - "scale-decode", - "scale-info", -] - [[package]] name = "merlin" version = "3.0.0" @@ -4673,7 +4644,6 @@ dependencies = [ "clap", "frame-benchmarking", "frame-benchmarking-cli", - "frame-metadata-hash-extension", "frame-system", "futures", "jsonrpsee", @@ -4729,7 +4699,6 @@ dependencies = [ "frame-benchmarking", "frame-executive", "frame-metadata", - "frame-metadata-hash-extension", "frame-support", "frame-system", "frame-system-benchmarking", @@ -4770,9 +4739,9 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", @@ -4983,7 +4952,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-weights", "substrate-fixed", "subtensor-macros", @@ -4992,7 +4961,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "frame-system", @@ -5003,13 +4972,13 @@ dependencies = [ "sp-application-crypto", "sp-consensus-aura", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-authorship" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "frame-system", @@ -5017,13 +4986,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-balances" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "frame-benchmarking", @@ -5033,7 +5002,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -5049,7 +5018,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "subtensor-macros", ] @@ -5067,14 +5036,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "subtensor-macros", ] [[package]] name = "pallet-grandpa" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -5091,13 +5060,13 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "frame-system", @@ -5105,13 +5074,13 @@ dependencies = [ "safe-mix", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-membership" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -5122,13 +5091,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-multisig" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -5138,13 +5107,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-preimage" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -5155,13 +5124,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-proxy" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -5170,7 +5139,7 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -5186,14 +5155,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "subtensor-macros", ] [[package]] name = "pallet-safe-mode" version = "9.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "frame-benchmarking", @@ -5206,13 +5175,13 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-scheduler" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "frame-benchmarking", @@ -5223,14 +5192,14 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-weights", ] [[package]] name = "pallet-session" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "frame-system", @@ -5245,7 +5214,7 @@ dependencies = [ "sp-session", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-trie", ] @@ -5277,8 +5246,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-version", "substrate-fixed", "subtensor-macros", @@ -5287,7 +5256,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "frame-benchmarking", @@ -5297,13 +5266,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-timestamp" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "frame-benchmarking", @@ -5315,15 +5284,15 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-timestamp", ] [[package]] name = "pallet-transaction-payment" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-support", "frame-system", @@ -5333,13 +5302,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "pallet-transaction-payment-rpc" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -5355,7 +5324,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -5367,7 +5336,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-benchmarking", "frame-support", @@ -5377,7 +5346,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -6715,18 +6684,18 @@ dependencies = [ [[package]] name = "sc-allocator" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "log", "sp-core", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "thiserror", ] [[package]] name = "sc-basic-authorship" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "futures", "futures-timer", @@ -6748,7 +6717,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "sp-api", @@ -6763,7 +6732,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "docify", @@ -6789,7 +6758,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -6800,7 +6769,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "chrono", @@ -6841,7 +6810,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "fnv", "futures", @@ -6856,11 +6825,11 @@ dependencies = [ "sp-consensus", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-runtime", "sp-state-machine", "sp-statement-store", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-trie", "substrate-prometheus-endpoint", ] @@ -6868,7 +6837,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "hash-db", "kvdb", @@ -6894,7 +6863,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "futures", @@ -6919,7 +6888,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "futures", @@ -6948,7 +6917,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ahash 0.8.11", "array-bytes 6.2.3", @@ -6991,7 +6960,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa-rpc" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "finality-grandpa", "futures", @@ -7011,7 +6980,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "futures", @@ -7034,7 +7003,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", @@ -7044,25 +7013,25 @@ dependencies = [ "schnellru", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-io", "sp-panic-handler", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-trie", "sp-version", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "tracing", ] [[package]] name = "sc-executor-common" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "polkavm", "sc-allocator", "sp-maybe-compressed-blob", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "thiserror", "wasm-instrument", ] @@ -7070,18 +7039,18 @@ dependencies = [ [[package]] name = "sc-executor-polkavm" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "log", "polkavm", "sc-executor-common", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "sc-executor-wasmtime" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "anyhow", "cfg-if", @@ -7091,15 +7060,15 @@ dependencies = [ "rustix 0.36.17", "sc-allocator", "sc-executor-common", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "wasmtime", ] [[package]] name = "sc-informant" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ansi_term", "futures", @@ -7116,7 +7085,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "25.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "parking_lot 0.12.3", @@ -7130,7 +7099,7 @@ dependencies = [ [[package]] name = "sc-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 4.2.0", "arrayvec", @@ -7159,7 +7128,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7202,7 +7171,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-channel", "cid", @@ -7222,7 +7191,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -7239,7 +7208,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ahash 0.8.11", "futures", @@ -7258,7 +7227,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7279,7 +7248,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7315,7 +7284,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7334,7 +7303,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "bytes", @@ -7357,7 +7326,7 @@ dependencies = [ "sc-utils", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-keystore", "sp-offchain", "sp-runtime", @@ -7368,7 +7337,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -7377,7 +7346,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "futures", "jsonrpsee", @@ -7409,7 +7378,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -7429,7 +7398,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "futures", "governor", @@ -7447,7 +7416,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7478,7 +7447,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "directories", @@ -7520,12 +7489,12 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", @@ -7542,7 +7511,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.30.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "log", "parity-scale-codec", @@ -7553,7 +7522,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "derive_more", "futures", @@ -7568,13 +7537,13 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "sc-telemetry" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "chrono", "futures", @@ -7593,7 +7562,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ansi_term", "chrono", @@ -7613,7 +7582,7 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "thiserror", "tracing", "tracing-log 0.1.4", @@ -7623,7 +7592,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -7634,7 +7603,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "futures", @@ -7652,7 +7621,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-transaction-pool", "substrate-prometheus-endpoint", "thiserror", @@ -7661,7 +7630,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "futures", @@ -7677,7 +7646,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-channel", "futures", @@ -7689,29 +7658,6 @@ dependencies = [ "sp-arithmetic", ] -[[package]] -name = "scale-bits" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" -dependencies = [ - "parity-scale-codec", - "scale-type-resolver", -] - -[[package]] -name = "scale-decode" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" -dependencies = [ - "derive_more", - "parity-scale-codec", - "scale-bits", - "scale-type-resolver", - "smallvec", -] - [[package]] name = "scale-info" version = "2.11.3" @@ -7738,12 +7684,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "scale-type-resolver" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" - [[package]] name = "schannel" version = "0.1.23" @@ -8189,7 +8129,7 @@ dependencies = [ [[package]] name = "sp-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "hash-db", "log", @@ -8197,12 +8137,12 @@ dependencies = [ "scale-info", "sp-api-proc-macro", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-metadata-ir", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-trie", "sp-version", "thiserror", @@ -8211,7 +8151,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "Inflector", "blake2 0.10.6", @@ -8225,20 +8165,20 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "scale-info", "serde", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "sp-arithmetic" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "integer-sqrt", @@ -8246,7 +8186,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "static_assertions", ] @@ -8271,7 +8211,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "sp-api", "sp-inherents", @@ -8281,7 +8221,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "futures", "log", @@ -8299,7 +8239,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "futures", @@ -8314,7 +8254,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "parity-scale-codec", @@ -8330,7 +8270,7 @@ dependencies = [ [[package]] name = "sp-consensus-grandpa" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "finality-grandpa", "log", @@ -8347,7 +8287,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "scale-info", @@ -8358,7 +8298,7 @@ dependencies = [ [[package]] name = "sp-core" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "array-bytes 6.2.3", "bandersnatch_vrfs", @@ -8389,11 +8329,11 @@ dependencies = [ "secrecy", "serde", "sp-crypto-hashing", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "ss58-registry", "substrate-bip39", "thiserror", @@ -8425,7 +8365,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "blake2b_simd", "byteorder", @@ -8438,7 +8378,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "quote", "sp-crypto-hashing", @@ -8448,7 +8388,7 @@ dependencies = [ [[package]] name = "sp-database" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "kvdb", "parking_lot 0.12.3", @@ -8457,7 +8397,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "proc-macro2", "quote", @@ -8477,11 +8417,11 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -8497,7 +8437,7 @@ dependencies = [ [[package]] name = "sp-genesis-builder" version = "0.7.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "serde_json", "sp-api", @@ -8507,7 +8447,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -8520,7 +8460,7 @@ dependencies = [ [[package]] name = "sp-io" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "bytes", "ed25519-dalek", @@ -8532,12 +8472,12 @@ dependencies = [ "secp256k1", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-keystore", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-trie", "tracing", "tracing-core", @@ -8546,7 +8486,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "31.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "sp-core", "sp-runtime", @@ -8556,18 +8496,18 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "thiserror", "zstd 0.12.4", @@ -8576,7 +8516,7 @@ dependencies = [ [[package]] name = "sp-metadata-ir" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-metadata", "parity-scale-codec", @@ -8586,7 +8526,7 @@ dependencies = [ [[package]] name = "sp-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "scale-info", @@ -8597,7 +8537,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "sp-api", "sp-core", @@ -8607,7 +8547,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "backtrace", "lazy_static", @@ -8617,7 +8557,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "rustc-hash", "serde", @@ -8627,7 +8567,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "31.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "docify", "either", @@ -8644,26 +8584,26 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-weights", ] [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive", "primitive-types", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "static_assertions", ] @@ -8689,7 +8629,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "Inflector", "expander", @@ -8715,7 +8655,7 @@ dependencies = [ [[package]] name = "sp-session" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "scale-info", @@ -8729,7 +8669,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -8742,7 +8682,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "hash-db", "log", @@ -8751,7 +8691,7 @@ dependencies = [ "rand", "smallvec", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-panic-handler", "sp-trie", "thiserror", @@ -8762,7 +8702,7 @@ dependencies = [ [[package]] name = "sp-statement-store" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "aes-gcm", "curve25519-dalek 4.1.3", @@ -8776,9 +8716,9 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "thiserror", "x25519-dalek 2.0.1", ] @@ -8786,7 +8726,7 @@ dependencies = [ [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" [[package]] name = "sp-std" @@ -8796,13 +8736,13 @@ source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06f [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "impl-serde", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -8820,7 +8760,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "parity-scale-codec", @@ -8832,7 +8772,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "tracing", @@ -8854,7 +8794,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "sp-api", "sp-runtime", @@ -8863,7 +8803,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "async-trait", "parity-scale-codec", @@ -8877,7 +8817,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "ahash 0.8.11", "hash-db", @@ -8890,7 +8830,7 @@ dependencies = [ "scale-info", "schnellru", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "thiserror", "tracing", "trie-db", @@ -8900,7 +8840,7 @@ dependencies = [ [[package]] name = "sp-version" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8909,7 +8849,7 @@ dependencies = [ "serde", "sp-crypto-hashing-proc-macro", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", "sp-version-proc-macro", "thiserror", ] @@ -8917,7 +8857,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -8928,7 +8868,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -8950,7 +8890,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -8958,7 +8898,7 @@ dependencies = [ "serde", "smallvec", "sp-arithmetic", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] [[package]] @@ -9097,7 +9037,7 @@ dependencies = [ [[package]] name = "substrate-bip39" version = "0.4.7" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "hmac 0.12.1", "pbkdf2", @@ -9109,7 +9049,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" [[package]] name = "substrate-fixed" @@ -9125,7 +9065,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -9144,7 +9084,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ "hyper", "log", @@ -9156,24 +9096,15 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" dependencies = [ - "array-bytes 6.2.3", "build-helper", "cargo_metadata", "console", "filetime", - "frame-metadata", - "merkleized-metadata", - "parity-scale-codec", "parity-wasm", "polkavm-linker", - "sc-executor", - "sp-core", - "sp-io", "sp-maybe-compressed-blob", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", - "sp-version", "strum 0.26.2", "tempfile", "toml 0.8.14", diff --git a/Cargo.toml b/Cargo.toml index 4a7565a01..8d9eff122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,81 +39,80 @@ litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } subtensor-macros = { path = "support/macros" } -frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" , default-features = false } -frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } substrate-fixed = { git = "https://github.com/encointer/substrate-fixed.git", tag = "v0.5.9" } -substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } frame-metadata = "16" [profile.release] diff --git a/justfile b/justfile index f99f3913a..e33fdf685 100644 --- a/justfile +++ b/justfile @@ -47,8 +47,4 @@ lint: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." just clippy-fix @echo "Running cargo clippy..." - just clippy - -production: - @echo "Running cargo build with metadata-hash generation..." - cargo +{{RUSTV}} build --profile production --features="runtime-benchmarks metadata-hash" + just clippy \ No newline at end of file diff --git a/node/Cargo.toml b/node/Cargo.toml index 0e1a418a3..7fc6eff48 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -54,7 +54,6 @@ sp-io = { workspace = true } sp-timestamp = { workspace = true } sp-inherents = { workspace = true } sp-keyring = { workspace = true } -frame-metadata-hash-extension = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-commitments = { path = "../pallets/commitments" } diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index cf48df62f..ba176e15f 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -136,7 +136,6 @@ pub fn create_benchmark_extrinsic( pallet_transaction_payment::ChargeTransactionPayment::::from(0), pallet_subtensor::SubtensorSignedExtension::::new(), pallet_commitments::CommitmentsSignedExtension::::new(), - frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -153,7 +152,6 @@ pub fn create_benchmark_extrinsic( (), (), (), - None, ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index c6c9ac481..03e087a92 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -80,7 +80,7 @@ benchmarks! { // This is a whitelisted caller who can make transaction without weights. let caller: T::AccountId = whitelisted_caller::>(); let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); - let netuid: u16 = 0; + let netuid: u16 = 1; let version_key: u64 = 1; let tempo: u16 = 1; let modality: u16 = 0; diff --git a/pallets/subtensor/src/epoch.rs b/pallets/subtensor/src/epoch.rs index 24ba3c784..12c407efa 100644 --- a/pallets/subtensor/src/epoch.rs +++ b/pallets/subtensor/src/epoch.rs @@ -188,6 +188,8 @@ impl Pallet { // ================================= // Compute emission scores. + + // Compute normalized emission scores. range: I32F32(0, 1) // Compute normalized emission scores. range: I32F32(0, 1) let combined_emission: Vec = incentive .iter() diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0f10bc581..f7824e2a3 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2045,10 +2045,9 @@ pub mod pallet { /// Attempt to adjust the senate membership to include a hotkey #[pallet::call_index(63)] - #[pallet::weight((Weight::from_parts(50_000_000, 0) - .saturating_add(Weight::from_parts(0, 4632)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(0, 0) + .saturating_add(T::DbWeight::get().reads(0)) + .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Normal, Pays::Yes))] pub fn adjust_senate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_adjust_senate(origin, hotkey) } diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/math.rs index 9aab2c4b5..1abc9ed9b 100644 --- a/pallets/subtensor/src/math.rs +++ b/pallets/subtensor/src/math.rs @@ -889,48 +889,59 @@ pub fn weighted_median( score: &[I32F32], partition_idx: &[usize], minority: I32F32, - mut partition_lo: I32F32, - mut partition_hi: I32F32, + partition_lo: I32F32, + partition_hi: I32F32, ) -> I32F32 { - let mut current_partition_idx = partition_idx.to_vec(); - while !current_partition_idx.is_empty() { - let n = current_partition_idx.len(); - if n == 1 { - return score[current_partition_idx[0]]; - } - let mid_idx: usize = n.saturating_div(2); - let pivot: I32F32 = score[current_partition_idx[mid_idx]]; - let mut lo_stake: I32F32 = I32F32::from_num(0); - let mut hi_stake: I32F32 = I32F32::from_num(0); - let mut lower: Vec = vec![]; - let mut upper: Vec = vec![]; - for &idx in ¤t_partition_idx { - if score[idx] == pivot { - continue; - } - if score[idx] < pivot { - lo_stake = lo_stake.saturating_add(stake[idx]); - lower.push(idx); - } else { - hi_stake = hi_stake.saturating_add(stake[idx]); - upper.push(idx); - } + let n = partition_idx.len(); + if n == 0 { + return I32F32::from_num(0); + } + if n == 1 { + return score[partition_idx[0]]; + } + assert!(stake.len() == score.len()); + let mid_idx: usize = n.saturating_div(2); + let pivot: I32F32 = score[partition_idx[mid_idx]]; + let mut lo_stake: I32F32 = I32F32::from_num(0); + let mut hi_stake: I32F32 = I32F32::from_num(0); + let mut lower: Vec = vec![]; + let mut upper: Vec = vec![]; + for &idx in partition_idx { + if score[idx] == pivot { + continue; } - if partition_lo.saturating_add(lo_stake) <= minority - && minority < partition_hi.saturating_sub(hi_stake) - { - return pivot; - } else if (minority < partition_lo.saturating_add(lo_stake)) && (!lower.is_empty()) { - current_partition_idx = lower; - partition_hi = partition_lo.saturating_add(lo_stake); - } else if (partition_hi.saturating_sub(hi_stake) <= minority) && (!upper.is_empty()) { - current_partition_idx = upper; - partition_lo = partition_hi.saturating_sub(hi_stake); + if score[idx] < pivot { + lo_stake = lo_stake.saturating_add(stake[idx]); + lower.push(idx); } else { - return pivot; + hi_stake = hi_stake.saturating_add(stake[idx]); + upper.push(idx); } } - I32F32::from_num(0) + if (partition_lo.saturating_add(lo_stake) <= minority) + && (minority < partition_hi.saturating_sub(hi_stake)) + { + return pivot; + } else if (minority < partition_lo.saturating_add(lo_stake)) && (!lower.is_empty()) { + return weighted_median( + stake, + score, + &lower, + minority, + partition_lo, + partition_lo.saturating_add(lo_stake), + ); + } else if (partition_hi.saturating_sub(hi_stake) <= minority) && (!upper.is_empty()) { + return weighted_median( + stake, + score, + &upper, + minority, + partition_hi.saturating_sub(hi_stake), + partition_hi, + ); + } + pivot } /// Column-wise weighted median, e.g. stake-weighted median scores per server (column) over all validators (rows). diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index aa2c4ad30..fc784f46f 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -1,13 +1,12 @@ #![allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] - +use frame_support::derive_impl; +use frame_support::dispatch::DispatchResultWithPostInfo; +use frame_support::weights::constants::RocksDbWeight; // use frame_support::weights::constants::WEIGHT_PER_SECOND; use frame_support::weights::Weight; use frame_support::{ - assert_ok, derive_impl, - dispatch::DispatchResultWithPostInfo, - parameter_types, + assert_ok, parameter_types, traits::{Everything, Hooks}, - weights::constants::RocksDbWeight, }; use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 042d0337c..fcb02a24c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -41,7 +41,6 @@ pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-utility = { workspace = true } frame-executive = { workspace = true } -frame-metadata-hash-extension = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } @@ -112,7 +111,6 @@ std = [ "codec/std", "scale-info/std", "frame-executive/std", - "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", @@ -206,4 +204,3 @@ try-runtime = [ "pallet-commitments/try-runtime", "pallet-registry/try-runtime" ] -metadata-hash = ["substrate-wasm-builder/metadata-hash"] diff --git a/runtime/build.rs b/runtime/build.rs index c0fa0405b..8f021e838 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -1,5 +1,5 @@ fn main() { - #[cfg(all(feature = "std", not(feature = "metadata-hash")))] + #[cfg(feature = "std")] { substrate_wasm_builder::WasmBuilder::new() .with_current_project() @@ -7,13 +7,4 @@ fn main() { .import_memory() .build(); } - #[cfg(all(feature = "std", feature = "metadata-hash"))] - { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .enable_metadata_hash("TAO", 9) - .build(); - } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b0b2fd736..a4abd124f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1282,7 +1282,6 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, pallet_subtensor::SubtensorSignedExtension, pallet_commitments::CommitmentsSignedExtension, - frame_metadata_hash_extension::CheckMetadataHash, ); type Migrations = pallet_grandpa::migrations::MigrateV4ToV5; diff --git a/scripts/build.sh b/scripts/build.sh index 3f588a1cc..548af664b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,2 +1,2 @@ -cargo build --profile production --features "runtime-benchmarks metadata-hash" +cargo build --profile production --features runtime-benchmarks From 59d9cbdc6b2034778fd238fa5c250d8801a4eda3 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 23 Jul 2024 02:06:56 +0400 Subject: [PATCH 185/185] chore: update spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..4079860b6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 165, + spec_version: 190, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,